From e77fd5d2861fc685d6fbdc5128ffb6f33731f9f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Tue, 5 Dec 2023 11:17:08 +0100 Subject: [PATCH] refactor: Switch plain errors in `nodes-base` to `ApplicationError` (no-changelog) (#7914) Ensure all errors in `nodes-base` are `ApplicationError` or children of it and contain no variables in the message, to continue normalizing all the backend errors we report to Sentry. Also, skip reporting to Sentry errors from user input and from external APIs. In future we should refine `ApplicationError` to more specific errors. Follow-up to: [#7877](https://github.com/n8n-io/n8n/pull/7877) - [x] Test workflows: https://github.com/n8n-io/n8n/actions/runs/7084627970 - [x] e2e: https://github.com/n8n-io/n8n/actions/runs/7084936861 --------- Co-authored-by: Michael Kret --- .../credentials/CustomerIoApi.credentials.ts | 3 ++- .../credentials/ZscalerZiaApi.credentials.ts | 5 +++- .../nodes/Airtable/v2/helpers/utils.ts | 8 +++--- .../nodes/Airtable/v2/transport/index.ts | 5 +++- .../nodes/Aws/DynamoDB/GenericFunctions.ts | 10 +++++--- .../nodes-base/nodes/Aws/DynamoDB/utils.ts | 4 +-- .../nodes/Code/test/Code.node.test.ts | 6 ++--- .../nodes/CompareDatasets/GenericFunctions.ts | 5 ++-- .../nodes/DebugHelper/DebugHelper.node.ts | 4 +-- .../nodes/FileMaker/GenericFunctions.ts | 4 +-- .../nodes/FormIo/GenericFunctions.ts | 5 ++-- .../nodes/Formstack/GenericFunctions.ts | 8 +++--- .../database/executeQuery.operation.ts | 5 ++-- .../Google/Sheet/v2/helpers/GoogleSheet.ts | 6 +++-- .../nodes/ItemLists/V3/helpers/utils.ts | 5 ++-- .../nodes/LoneScale/GenericFunctions.ts | 18 +++++++------ .../nodes/Magento/GenericFunctions.ts | 4 +-- .../nodes/Mailcheck/GenericFunctions.ts | 5 +++- .../nodes/Merge/v2/GenericFunctions.ts | 12 ++++++--- .../Microsoft/Outlook/v2/helpers/utils.ts | 6 +++-- .../nodes-base/nodes/MongoDb/MongoDb.node.ts | 6 +++-- .../nodes/MySql/v2/transport/index.ts | 9 +++++-- .../Postgres/PostgresTrigger.functions.ts | 9 ++++--- .../nodes/Postgres/v1/genericFunctions.ts | 25 ++++++++++++++----- .../nodes-base/nodes/Set/v2/helpers/utils.ts | 12 +++++++-- .../nodes/TheHive/GenericFunctions.ts | 6 ++--- .../nodes/TheHiveProject/helpers/utils.ts | 6 +++-- .../nodes/Todoist/v1/OperationHandler.ts | 7 ++++-- .../nodes/Todoist/v2/OperationHandler.ts | 7 ++++-- .../nodes/Twitter/V2/GenericFunctions.ts | 9 ++++--- .../Venafi/Datacenter/GenericFunctions.ts | 6 ++++- packages/nodes-base/test/nodes/Helpers.ts | 14 ++++++----- packages/nodes-base/utils/utilities.ts | 6 ++--- 33 files changed, 164 insertions(+), 86 deletions(-) diff --git a/packages/nodes-base/credentials/CustomerIoApi.credentials.ts b/packages/nodes-base/credentials/CustomerIoApi.credentials.ts index 7ac91eefb..fe805e4af 100644 --- a/packages/nodes-base/credentials/CustomerIoApi.credentials.ts +++ b/packages/nodes-base/credentials/CustomerIoApi.credentials.ts @@ -1,3 +1,4 @@ +import { ApplicationError } from 'n8n-workflow'; import type { ICredentialDataDecryptedObject, ICredentialType, @@ -76,7 +77,7 @@ export class CustomerIoApi implements ICredentialType { Authorization: `Bearer ${credentials.appApiKey as string}`, }); } else { - throw new Error('Unknown way of authenticating'); + throw new ApplicationError('Unknown way of authenticating', { level: 'warning' }); } return requestOptions; diff --git a/packages/nodes-base/credentials/ZscalerZiaApi.credentials.ts b/packages/nodes-base/credentials/ZscalerZiaApi.credentials.ts index 6f783a103..bd3260442 100644 --- a/packages/nodes-base/credentials/ZscalerZiaApi.credentials.ts +++ b/packages/nodes-base/credentials/ZscalerZiaApi.credentials.ts @@ -1,3 +1,4 @@ +import { ApplicationError } from 'n8n-workflow'; import type { IAuthenticateGeneric, ICredentialDataDecryptedObject, @@ -120,7 +121,9 @@ export class ZscalerZiaApi implements ICredentialType { ?.find((entry) => entry.includes('JSESSIONID')); if (!cookie) { - throw new Error('No cookie returned. Please check your credentials.'); + throw new ApplicationError('No cookie returned. Please check your credentials.', { + level: 'warning', + }); } return { cookie }; diff --git a/packages/nodes-base/nodes/Airtable/v2/helpers/utils.ts b/packages/nodes-base/nodes/Airtable/v2/helpers/utils.ts index fb43c3df4..ff1a18624 100644 --- a/packages/nodes-base/nodes/Airtable/v2/helpers/utils.ts +++ b/packages/nodes-base/nodes/Airtable/v2/helpers/utils.ts @@ -1,4 +1,4 @@ -import type { IDataObject, NodeApiError } from 'n8n-workflow'; +import { ApplicationError, type IDataObject, type NodeApiError } from 'n8n-workflow'; import type { UpdateRecord } from './interfaces'; export function removeIgnored(data: IDataObject, ignore: string | string[]) { @@ -42,7 +42,7 @@ export function findMatches( }); if (!matches?.length) { - throw new Error('No records match provided keys'); + throw new ApplicationError('No records match provided keys', { level: 'warning' }); } return matches; @@ -57,7 +57,9 @@ export function findMatches( }); if (!match) { - throw new Error('Record matching provided keys was not found'); + throw new ApplicationError('Record matching provided keys was not found', { + level: 'warning', + }); } return [match]; diff --git a/packages/nodes-base/nodes/Airtable/v2/transport/index.ts b/packages/nodes-base/nodes/Airtable/v2/transport/index.ts index 63ea8757f..44aa041a6 100644 --- a/packages/nodes-base/nodes/Airtable/v2/transport/index.ts +++ b/packages/nodes-base/nodes/Airtable/v2/transport/index.ts @@ -8,6 +8,7 @@ import type { ILoadOptionsFunctions, INodeExecutionData, } from 'n8n-workflow'; +import { ApplicationError } from 'n8n-workflow'; import type { IAttachment, IRecord } from '../helpers/interfaces'; import { flattenOutput } from '../helpers/utils'; @@ -91,7 +92,9 @@ export async function downloadRecordAttachments( fieldNames = fieldNames.split(',').map((item) => item.trim()); } if (!fieldNames.length) { - throw new Error("Specify field to download in 'Download Attachments' option"); + throw new ApplicationError("Specify field to download in 'Download Attachments' option", { + level: 'warning', + }); } const elements: INodeExecutionData[] = []; for (const record of records) { diff --git a/packages/nodes-base/nodes/Aws/DynamoDB/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/DynamoDB/GenericFunctions.ts index ded521517..eb1a6df22 100644 --- a/packages/nodes-base/nodes/Aws/DynamoDB/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/DynamoDB/GenericFunctions.ts @@ -7,7 +7,7 @@ import type { IHttpRequestOptions, INodeExecutionData, } from 'n8n-workflow'; -import { deepCopy } from 'n8n-workflow'; +import { ApplicationError, deepCopy } from 'n8n-workflow'; import type { IRequestBody } from './types'; @@ -43,13 +43,13 @@ export async function awsApiRequest( if (statusCode === 403) { if (errorMessage === 'The security token included in the request is invalid.') { - throw new Error('The AWS credentials are not valid!'); + throw new ApplicationError('The AWS credentials are not valid!', { level: 'warning' }); } else if ( errorMessage.startsWith( 'The request signature we calculated does not match the signature you provided', ) ) { - throw new Error('The AWS credentials are not valid!'); + throw new ApplicationError('The AWS credentials are not valid!', { level: 'warning' }); } } @@ -59,7 +59,9 @@ export async function awsApiRequest( } catch (ex) {} } - throw new Error(`AWS error response [${statusCode}]: ${errorMessage}`); + throw new ApplicationError(`AWS error response [${statusCode}]: ${errorMessage}`, { + level: 'warning', + }); } } diff --git a/packages/nodes-base/nodes/Aws/DynamoDB/utils.ts b/packages/nodes-base/nodes/Aws/DynamoDB/utils.ts index a55e9ff46..eb7c3f4d4 100644 --- a/packages/nodes-base/nodes/Aws/DynamoDB/utils.ts +++ b/packages/nodes-base/nodes/Aws/DynamoDB/utils.ts @@ -1,5 +1,5 @@ import type { IDataObject, INodeExecutionData } from 'n8n-workflow'; -import { deepCopy, assert } from 'n8n-workflow'; +import { deepCopy, assert, ApplicationError } from 'n8n-workflow'; import type { AdjustedPutItem, @@ -98,7 +98,7 @@ export function validateJSON(input: any): object { try { return JSON.parse(input as string); } catch (error) { - throw new Error('Items must be a valid JSON'); + throw new ApplicationError('Items must be a valid JSON', { level: 'warning' }); } } diff --git a/packages/nodes-base/nodes/Code/test/Code.node.test.ts b/packages/nodes-base/nodes/Code/test/Code.node.test.ts index 9c57f9c60..2b827877e 100644 --- a/packages/nodes-base/nodes/Code/test/Code.node.test.ts +++ b/packages/nodes-base/nodes/Code/test/Code.node.test.ts @@ -1,7 +1,7 @@ import { anyNumber, mock } from 'jest-mock-extended'; import { NodeVM } from '@n8n/vm2'; import type { IExecuteFunctions, IWorkflowDataProxyData } from 'n8n-workflow'; -import { NodeHelpers } from 'n8n-workflow'; +import { ApplicationError, NodeHelpers } from 'n8n-workflow'; import { normalizeItems } from 'n8n-core'; import { Code } from '../Code.node'; import { ValidationError } from '../ValidationError'; @@ -79,7 +79,7 @@ describe('Code Node unit test', () => { try { await node.execute.call(thisArg); - throw new Error("Validation error wasn't thrown"); + throw new ApplicationError("Validation error wasn't thrown", { level: 'warning' }); } catch (error) { expect(error).toBeInstanceOf(ValidationError); expect(error.message).toEqual("A 'json' property isn't an object [item 0]"); @@ -131,7 +131,7 @@ describe('Code Node unit test', () => { try { await node.execute.call(thisArg); - throw new Error("Validation error wasn't thrown"); + throw new ApplicationError("Validation error wasn't thrown", { level: 'warning' }); } catch (error) { expect(error).toBeInstanceOf(ValidationError); expect(error.message).toEqual("A 'json' property isn't an object [item 0]"); diff --git a/packages/nodes-base/nodes/CompareDatasets/GenericFunctions.ts b/packages/nodes-base/nodes/CompareDatasets/GenericFunctions.ts index c89ac6404..85b6eecda 100644 --- a/packages/nodes-base/nodes/CompareDatasets/GenericFunctions.ts +++ b/packages/nodes-base/nodes/CompareDatasets/GenericFunctions.ts @@ -1,4 +1,4 @@ -import type { IDataObject, INodeExecutionData } from 'n8n-workflow'; +import { ApplicationError, type IDataObject, type INodeExecutionData } from 'n8n-workflow'; import difference from 'lodash/difference'; import get from 'lodash/get'; @@ -102,8 +102,9 @@ function compareItems( skippedFieldsWithDotNotation.length && (typeof input1 !== 'object' || typeof input2 !== 'object') ) { - throw new Error( + throw new ApplicationError( `The field \'${key}\' in item ${i} is not an object. It is not possible to use dot notation.`, + { level: 'warning' }, ); } diff --git a/packages/nodes-base/nodes/DebugHelper/DebugHelper.node.ts b/packages/nodes-base/nodes/DebugHelper/DebugHelper.node.ts index 76a616591..6d657f664 100644 --- a/packages/nodes-base/nodes/DebugHelper/DebugHelper.node.ts +++ b/packages/nodes-base/nodes/DebugHelper/DebugHelper.node.ts @@ -4,7 +4,7 @@ import type { INodeType, INodeTypeDescription, } from 'n8n-workflow'; -import { NodeApiError, NodeOperationError } from 'n8n-workflow'; +import { ApplicationError, NodeApiError, NodeOperationError } from 'n8n-workflow'; import { setSeed, array as mfArray } from 'minifaker'; import { generateCreditCard, @@ -273,7 +273,7 @@ export class DebugHelper implements INodeType { }); case 'Error': // eslint-disable-next-line n8n-nodes-base/node-execute-block-wrong-error-thrown - throw new Error(throwErrorMessage); + throw new ApplicationError(throwErrorMessage); default: break; } diff --git a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts index bdb13b109..52e0faac6 100644 --- a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts +++ b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts @@ -5,7 +5,7 @@ import type { INodePropertyOptions, JsonObject, } from 'n8n-workflow'; -import { NodeApiError, NodeOperationError } from 'n8n-workflow'; +import { ApplicationError, NodeApiError, NodeOperationError } from 'n8n-workflow'; import type { OptionsWithUri } from 'request'; @@ -81,7 +81,7 @@ export async function getToken(this: ILoadOptionsFunctions | IExecuteFunctions): } else { message = error.message; } - throw new Error(message); + throw new ApplicationError(message, { level: 'warning' }); } } diff --git a/packages/nodes-base/nodes/FormIo/GenericFunctions.ts b/packages/nodes-base/nodes/FormIo/GenericFunctions.ts index be82c1c98..e4ff44f3e 100644 --- a/packages/nodes-base/nodes/FormIo/GenericFunctions.ts +++ b/packages/nodes-base/nodes/FormIo/GenericFunctions.ts @@ -5,7 +5,7 @@ import type { IWebhookFunctions, JsonObject, } from 'n8n-workflow'; -import { NodeApiError } from 'n8n-workflow'; +import { ApplicationError, NodeApiError } from 'n8n-workflow'; interface IFormIoCredentials { environment: 'cloudHosted' | ' selfHosted'; @@ -42,8 +42,9 @@ async function getToken( const responseObject = await this.helpers.request(options); return responseObject.headers['x-jwt-token']; } catch (error) { - throw new Error( + throw new ApplicationError( 'Authentication Failed for Form.io. Please provide valid credentails/ endpoint details', + { level: 'warning' }, ); } } diff --git a/packages/nodes-base/nodes/Formstack/GenericFunctions.ts b/packages/nodes-base/nodes/Formstack/GenericFunctions.ts index de1938413..d4dbadf30 100644 --- a/packages/nodes-base/nodes/Formstack/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Formstack/GenericFunctions.ts @@ -7,7 +7,7 @@ import type { INodePropertyOptions, JsonObject, } from 'n8n-workflow'; -import { NodeApiError } from 'n8n-workflow'; +import { ApplicationError, NodeApiError } from 'n8n-workflow'; import type { OptionsWithUri } from 'request'; @@ -136,7 +136,7 @@ export async function getForms(this: ILoadOptionsFunctions): Promise error.message); - throw new Error( + throw new ApplicationError( `Error(s) ocurring while executing query from item ${job.i.toString()}: ${errorMessages.join( ', ', )}`, + { level: 'warning' }, ); } } catch (error) { diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheet.ts b/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheet.ts index fe1983a2a..18696af58 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheet.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheet.ts @@ -5,7 +5,7 @@ import type { IPollFunctions, INode, } from 'n8n-workflow'; -import { NodeOperationError } from 'n8n-workflow'; +import { ApplicationError, NodeOperationError } from 'n8n-workflow'; import { utils as xlsxUtils } from 'xlsx'; import get from 'lodash/get'; import { apiRequest } from '../transport'; @@ -226,7 +226,9 @@ export class GoogleSheet { } if (requests.length === 0) { - throw new Error('Must specify at least one column or row to add'); + throw new ApplicationError('Must specify at least one column or row to add', { + level: 'warning', + }); } const response = await apiRequest.call( diff --git a/packages/nodes-base/nodes/ItemLists/V3/helpers/utils.ts b/packages/nodes-base/nodes/ItemLists/V3/helpers/utils.ts index 7a22c6e55..49b648527 100644 --- a/packages/nodes-base/nodes/ItemLists/V3/helpers/utils.ts +++ b/packages/nodes-base/nodes/ItemLists/V3/helpers/utils.ts @@ -7,7 +7,7 @@ import type { INodeExecutionData, GenericValue, } from 'n8n-workflow'; -import { NodeOperationError } from 'n8n-workflow'; +import { ApplicationError, NodeOperationError } from 'n8n-workflow'; import get from 'lodash/get'; import isEqual from 'lodash/isEqual'; @@ -62,8 +62,9 @@ export const prepareFieldsArray = (fields: string | string[], fieldName = 'Field if (Array.isArray(fields)) { return fields; } - throw new Error( + throw new ApplicationError( `The \'${fieldName}\' parameter must be a string of fields separated by commas or an array of strings.`, + { level: 'warning' }, ); }; diff --git a/packages/nodes-base/nodes/LoneScale/GenericFunctions.ts b/packages/nodes-base/nodes/LoneScale/GenericFunctions.ts index 2f8767a18..df82c7e06 100644 --- a/packages/nodes-base/nodes/LoneScale/GenericFunctions.ts +++ b/packages/nodes-base/nodes/LoneScale/GenericFunctions.ts @@ -1,11 +1,12 @@ import type { OptionsWithUri } from 'request'; -import type { - IDataObject, - IExecuteFunctions, - IHookFunctions, - ILoadOptionsFunctions, - IWebhookFunctions, +import { + ApplicationError, + type IDataObject, + type IExecuteFunctions, + type IHookFunctions, + type ILoadOptionsFunctions, + type IWebhookFunctions, } from 'n8n-workflow'; import { BASE_URL } from './constants'; @@ -43,7 +44,10 @@ export async function lonescaleApiRequest( if (error.response) { const errorMessage = error.response.body.message || error.response.body.description || error.message; - throw new Error(`Autopilot error response [${error.statusCode}]: ${errorMessage}`); + throw new ApplicationError( + `Autopilot error response [${error.statusCode}]: ${errorMessage}`, + { level: 'warning' }, + ); } throw error; } diff --git a/packages/nodes-base/nodes/Magento/GenericFunctions.ts b/packages/nodes-base/nodes/Magento/GenericFunctions.ts index 8a09f8b45..3e3071cc7 100644 --- a/packages/nodes-base/nodes/Magento/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Magento/GenericFunctions.ts @@ -10,7 +10,7 @@ import type { INodePropertyOptions, JsonObject, } from 'n8n-workflow'; -import { NodeApiError } from 'n8n-workflow'; +import { ApplicationError, NodeApiError } from 'n8n-workflow'; import type { Filter, Address, Search, FilterGroup, ProductAttribute } from './types'; export async function magentoApiRequest( @@ -480,7 +480,7 @@ export function getFilterQuery(data: { sort: [{ direction: string; field: string }]; }): Search { if (!data.hasOwnProperty('conditions') || data.conditions?.length === 0) { - throw new Error('At least one filter has to be set'); + throw new ApplicationError('At least one filter has to be set', { level: 'warning' }); } if (data.matchType === 'anyFilter') { diff --git a/packages/nodes-base/nodes/Mailcheck/GenericFunctions.ts b/packages/nodes-base/nodes/Mailcheck/GenericFunctions.ts index 657c4d077..e582627f9 100644 --- a/packages/nodes-base/nodes/Mailcheck/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mailcheck/GenericFunctions.ts @@ -1,5 +1,7 @@ import type { OptionsWithUri } from 'request'; +import { ApplicationError } from 'n8n-workflow'; + import type { IDataObject, IExecuteFunctions, @@ -44,8 +46,9 @@ export async function mailCheckApiRequest( } catch (error) { if (error.response?.body?.message) { // Try to return the error prettier - throw new Error( + throw new ApplicationError( `Mailcheck error response [${error.statusCode}]: ${error.response.body.message}`, + { level: 'warning' }, ); } throw error; diff --git a/packages/nodes-base/nodes/Merge/v2/GenericFunctions.ts b/packages/nodes-base/nodes/Merge/v2/GenericFunctions.ts index 83d5a3dee..5255ffa39 100644 --- a/packages/nodes-base/nodes/Merge/v2/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Merge/v2/GenericFunctions.ts @@ -1,3 +1,4 @@ +import { ApplicationError } from 'n8n-workflow'; import type { GenericValue, IBinaryKeyData, @@ -332,16 +333,18 @@ export function mergeMatched( export function checkMatchFieldsInput(data: IDataObject[]) { if (data.length === 1 && data[0].field1 === '' && data[0].field2 === '') { - throw new Error( + throw new ApplicationError( 'You need to define at least one pair of fields in "Fields to Match" to match on', + { level: 'warning' }, ); } for (const [index, pair] of data.entries()) { if (pair.field1 === '' || pair.field2 === '') { - throw new Error( + throw new ApplicationError( `You need to define both fields in "Fields to Match" for pair ${index + 1}, field 1 = '${pair.field1}' field 2 = '${pair.field2}'`, + { level: 'warning' }, ); } } @@ -362,7 +365,10 @@ export function checkInput( return get(entry.json, field, undefined) !== undefined; }); if (!isPresent) { - throw new Error(`Field '${field}' is not present in any of items in '${inputLabel}'`); + throw new ApplicationError( + `Field '${field}' is not present in any of items in '${inputLabel}'`, + { level: 'warning' }, + ); } } return input; diff --git a/packages/nodes-base/nodes/Microsoft/Outlook/v2/helpers/utils.ts b/packages/nodes-base/nodes/Microsoft/Outlook/v2/helpers/utils.ts index 3ea607c1d..85ebecdd9 100644 --- a/packages/nodes-base/nodes/Microsoft/Outlook/v2/helpers/utils.ts +++ b/packages/nodes-base/nodes/Microsoft/Outlook/v2/helpers/utils.ts @@ -5,7 +5,7 @@ import type { ILoadOptionsFunctions, JsonObject, } from 'n8n-workflow'; -import { jsonParse, NodeApiError } from 'n8n-workflow'; +import { ApplicationError, jsonParse, NodeApiError } from 'n8n-workflow'; export const messageFields = [ 'bccRecipients', @@ -157,7 +157,9 @@ export function createMessage(fields: IDataObject) { } else if (typeof value === 'string') { message[key] = value.split(',').map((recipient: string) => makeRecipient(recipient.trim())); } else { - throw new Error(`The "${key}" field must be a string or an array of strings`); + throw new ApplicationError(`The "${key}" field must be a string or an array of strings`, { + level: 'warning', + }); } continue; } diff --git a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts index ecd718008..304abe6c3 100644 --- a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts +++ b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts @@ -9,7 +9,7 @@ import type { INodeTypeDescription, JsonObject, } from 'n8n-workflow'; -import { NodeOperationError } from 'n8n-workflow'; +import { ApplicationError, NodeOperationError } from 'n8n-workflow'; import type { FindOneAndReplaceOptions, @@ -80,7 +80,9 @@ export class MongoDb implements INodeType { if (!(databases as IDataObject[]).map((db) => db.name).includes(database)) { // eslint-disable-next-line n8n-nodes-base/node-execute-block-wrong-error-thrown - throw new Error(`Database "${database}" does not exist`); + throw new ApplicationError(`Database "${database}" does not exist`, { + level: 'warning', + }); } await client.close(); } catch (error) { diff --git a/packages/nodes-base/nodes/MySql/v2/transport/index.ts b/packages/nodes-base/nodes/MySql/v2/transport/index.ts index 4a01a386c..5abf51a21 100644 --- a/packages/nodes-base/nodes/MySql/v2/transport/index.ts +++ b/packages/nodes-base/nodes/MySql/v2/transport/index.ts @@ -1,3 +1,4 @@ +import { ApplicationError } from 'n8n-workflow'; import type { ICredentialDataDecryptedObject, IDataObject } from 'n8n-workflow'; import mysql2 from 'mysql2/promise'; @@ -36,7 +37,9 @@ export async function createPool( sshClient?: Client, ): Promise { if (credentials === undefined) { - throw new Error('Credentials not selected, select or add new credentials'); + throw new ApplicationError('Credentials not selected, select or add new credentials', { + level: 'warning', + }); } const { ssl, @@ -94,7 +97,9 @@ export async function createPool( return mysql2.createPool(connectionOptions); } else { if (!sshClient) { - throw new Error('SSH Tunnel is enabled but no SSH Client was provided'); + throw new ApplicationError('SSH Tunnel is enabled but no SSH Client was provided', { + level: 'warning', + }); } const tunnelConfig = await createSshConnectConfig(credentials); diff --git a/packages/nodes-base/nodes/Postgres/PostgresTrigger.functions.ts b/packages/nodes-base/nodes/Postgres/PostgresTrigger.functions.ts index e27b863b4..46bf31dba 100644 --- a/packages/nodes-base/nodes/Postgres/PostgresTrigger.functions.ts +++ b/packages/nodes-base/nodes/Postgres/PostgresTrigger.functions.ts @@ -1,3 +1,4 @@ +import { ApplicationError } from 'n8n-workflow'; import type { ITriggerFunctions, IDataObject, @@ -26,7 +27,7 @@ export function prepareNames(id: string, mode: string, additionalFields: IDataOb const channelName = (additionalFields.channelName as string) || `n8n_channel_${suffix}`; if (channelName.includes('-')) { - throw new Error('Channel name cannot contain hyphens (-)'); + throw new ApplicationError('Channel name cannot contain hyphens (-)', { level: 'warning' }); } return { functionName, triggerName, channelName }; @@ -63,7 +64,7 @@ export async function pgTriggerFunction( const whichData = firesOn === 'DELETE' ? 'old' : 'new'; if (channelName.includes('-')) { - throw new Error('Channel name cannot contain hyphens (-)'); + throw new ApplicationError('Channel name cannot contain hyphens (-)', { level: 'warning' }); } const replaceIfExists = additionalFields.replaceIfExists ?? false; @@ -78,7 +79,7 @@ export async function pgTriggerFunction( await db.any(trigger, [target, functionName, firesOn, triggerName]); } catch (error) { if ((error as Error).message.includes('near "-"')) { - throw new Error('Names cannot contain hyphens (-)'); + throw new ApplicationError('Names cannot contain hyphens (-)', { level: 'warning' }); } throw error; } @@ -132,7 +133,7 @@ export async function searchTables(this: ILoadOptionsFunctions): Promise ({ name: s.table_name as string, diff --git a/packages/nodes-base/nodes/Postgres/v1/genericFunctions.ts b/packages/nodes-base/nodes/Postgres/v1/genericFunctions.ts index edf4f02a6..1f858abcd 100644 --- a/packages/nodes-base/nodes/Postgres/v1/genericFunctions.ts +++ b/packages/nodes-base/nodes/Postgres/v1/genericFunctions.ts @@ -1,3 +1,4 @@ +import { ApplicationError } from 'n8n-workflow'; import type { IExecuteFunctions, IDataObject, INodeExecutionData, JsonObject } from 'n8n-workflow'; import type pgPromise from 'pg-promise'; import type pg from 'pg-promise/typescript/pg-subset'; @@ -160,7 +161,9 @@ export async function pgQuery( return result; }); } - throw new Error('multiple, independently or transaction are valid options'); + throw new ApplicationError('multiple, independently or transaction are valid options', { + level: 'warning', + }); } export async function pgQueryV2( @@ -259,7 +262,9 @@ export async function pgQueryV2( return result; }); } - throw new Error('multiple, independently or transaction are valid options'); + throw new ApplicationError('multiple, independently or transaction are valid options', { + level: 'warning', + }); } /** @@ -348,7 +353,9 @@ export async function pgInsert( }); } - throw new Error('multiple, independently or transaction are valid options'); + throw new ApplicationError('multiple, independently or transaction are valid options', { + level: 'warning', + }); } /** @@ -456,7 +463,9 @@ export async function pgInsertV2( }); } - throw new Error('multiple, independently or transaction are valid options'); + throw new ApplicationError('multiple, independently or transaction are valid options', { + level: 'warning', + }); } /** @@ -582,7 +591,9 @@ export async function pgUpdate( }); } } - throw new Error('multiple, independently or transaction are valid options'); + throw new ApplicationError('multiple, independently or transaction are valid options', { + level: 'warning', + }); } /** @@ -713,5 +724,7 @@ export async function pgUpdateV2( }); } } - throw new Error('multiple, independently or transaction are valid options'); + throw new ApplicationError('multiple, independently or transaction are valid options', { + level: 'warning', + }); } diff --git a/packages/nodes-base/nodes/Set/v2/helpers/utils.ts b/packages/nodes-base/nodes/Set/v2/helpers/utils.ts index e41715145..fd4f3c62b 100644 --- a/packages/nodes-base/nodes/Set/v2/helpers/utils.ts +++ b/packages/nodes-base/nodes/Set/v2/helpers/utils.ts @@ -5,7 +5,13 @@ import type { INode, INodeExecutionData, } from 'n8n-workflow'; -import { deepCopy, NodeOperationError, jsonParse, validateFieldType } from 'n8n-workflow'; +import { + deepCopy, + NodeOperationError, + jsonParse, + validateFieldType, + ApplicationError, +} from 'n8n-workflow'; import set from 'lodash/set'; import get from 'lodash/get'; @@ -101,7 +107,9 @@ export function composeReturnItem( case INCLUDE.NONE: break; default: - throw new Error(`The include option "${options.include}" is not known!`); + throw new ApplicationError(`The include option "${options.include}" is not known!`, { + level: 'warning', + }); } for (const key of Object.keys(newFields)) { diff --git a/packages/nodes-base/nodes/TheHive/GenericFunctions.ts b/packages/nodes-base/nodes/TheHive/GenericFunctions.ts index fab2dc861..17e3d0bf2 100644 --- a/packages/nodes-base/nodes/TheHive/GenericFunctions.ts +++ b/packages/nodes-base/nodes/TheHive/GenericFunctions.ts @@ -6,7 +6,7 @@ import type { ILoadOptionsFunctions, IDataObject, } from 'n8n-workflow'; -import { jsonParse } from 'n8n-workflow'; +import { ApplicationError, jsonParse } from 'n8n-workflow'; import moment from 'moment'; import { Eq } from './QueryFunctions'; @@ -79,7 +79,7 @@ export function prepareOptional(optionals: IDataObject): IDataObject { try { response[key] = jsonParse(optionals[key] as string); } catch (error) { - throw new Error('Invalid JSON for artifacts'); + throw new ApplicationError('Invalid JSON for artifacts', { level: 'warning' }); } } else if (key === 'tags') { response[key] = splitTags(optionals[key] as string); @@ -107,7 +107,7 @@ export async function prepareCustomFields( try { customFieldsJson = jsonParse(customFieldsJson); } catch (error) { - throw new Error('Invalid JSON for customFields'); + throw new ApplicationError('Invalid JSON for customFields', { level: 'warning' }); } } diff --git a/packages/nodes-base/nodes/TheHiveProject/helpers/utils.ts b/packages/nodes-base/nodes/TheHiveProject/helpers/utils.ts index 2299e35a3..8c392043b 100644 --- a/packages/nodes-base/nodes/TheHiveProject/helpers/utils.ts +++ b/packages/nodes-base/nodes/TheHiveProject/helpers/utils.ts @@ -1,4 +1,4 @@ -import type { IDataObject } from 'n8n-workflow'; +import { ApplicationError, type IDataObject } from 'n8n-workflow'; import get from 'lodash/get'; import set from 'lodash/set'; @@ -54,7 +54,9 @@ export function prepareInputItem(item: IDataObject, schema: IDataObject[], i: nu set(returnData, id, value); } else { if (entry.required) { - throw new Error(`Required field "${id}" is missing in item ${i}`); + throw new ApplicationError(`Required field "${id}" is missing in item ${i}`, { + level: 'warning', + }); } } } diff --git a/packages/nodes-base/nodes/Todoist/v1/OperationHandler.ts b/packages/nodes-base/nodes/Todoist/v1/OperationHandler.ts index 93fd82fc6..b4cd4f2ec 100644 --- a/packages/nodes-base/nodes/Todoist/v1/OperationHandler.ts +++ b/packages/nodes-base/nodes/Todoist/v1/OperationHandler.ts @@ -1,5 +1,5 @@ import type { IDataObject } from 'n8n-workflow'; -import { jsonParse } from 'n8n-workflow'; +import { ApplicationError, jsonParse } from 'n8n-workflow'; import { v4 as uuid } from 'uuid'; import type { Context } from '../GenericFunctions'; import { FormatDueDatetime, todoistApiRequest, todoistSyncRequest } from '../GenericFunctions'; @@ -323,7 +323,10 @@ export class SyncHandler implements OperationHandler { if (sectionId) { command.args.section_id = sectionId; } else { - throw new Error('Section ' + command.args.section + " doesn't exist on Todoist"); + throw new ApplicationError( + 'Section ' + command.args.section + " doesn't exist on Todoist", + { level: 'warning' }, + ); } } } diff --git a/packages/nodes-base/nodes/Todoist/v2/OperationHandler.ts b/packages/nodes-base/nodes/Todoist/v2/OperationHandler.ts index f3bb67ce9..19e9c661a 100644 --- a/packages/nodes-base/nodes/Todoist/v2/OperationHandler.ts +++ b/packages/nodes-base/nodes/Todoist/v2/OperationHandler.ts @@ -1,5 +1,5 @@ import type { IDataObject } from 'n8n-workflow'; -import { jsonParse } from 'n8n-workflow'; +import { ApplicationError, jsonParse } from 'n8n-workflow'; import { v4 as uuid } from 'uuid'; import type { Context } from '../GenericFunctions'; import { FormatDueDatetime, todoistApiRequest, todoistSyncRequest } from '../GenericFunctions'; @@ -317,7 +317,10 @@ export class SyncHandler implements OperationHandler { if (sectionId) { command.args.section_id = sectionId; } else { - throw new Error('Section ' + command.args.section + " doesn't exist on Todoist"); + throw new ApplicationError( + 'Section ' + command.args.section + " doesn't exist on Todoist", + { level: 'warning' }, + ); } } } diff --git a/packages/nodes-base/nodes/Twitter/V2/GenericFunctions.ts b/packages/nodes-base/nodes/Twitter/V2/GenericFunctions.ts index 7227b1cca..353de66b0 100644 --- a/packages/nodes-base/nodes/Twitter/V2/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Twitter/V2/GenericFunctions.ts @@ -8,7 +8,7 @@ import type { INodeParameterResourceLocator, JsonObject, } from 'n8n-workflow'; -import { NodeApiError, NodeOperationError } from 'n8n-workflow'; +import { ApplicationError, NodeApiError, NodeOperationError } from 'n8n-workflow'; export async function twitterApiRequest( this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions, @@ -90,7 +90,7 @@ export function returnId(tweetId: INodeParameterResourceLocator) { return tweetIdMatch?.[3] as string; } else { - throw new Error(`The mode ${tweetId.mode} is not valid!`); + throw new ApplicationError(`The mode ${tweetId.mode} is not valid!`, { level: 'warning' }); } } @@ -120,5 +120,8 @@ export async function returnIdFromUsername( {}, )) as { id: string }; return list.id; - } else throw new Error(`The username mode ${usernameRlc.mode} is not valid!`); + } else + throw new ApplicationError(`The username mode ${usernameRlc.mode} is not valid!`, { + level: 'warning', + }); } diff --git a/packages/nodes-base/nodes/Venafi/Datacenter/GenericFunctions.ts b/packages/nodes-base/nodes/Venafi/Datacenter/GenericFunctions.ts index e7532e30b..ecb654ceb 100644 --- a/packages/nodes-base/nodes/Venafi/Datacenter/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Venafi/Datacenter/GenericFunctions.ts @@ -1,5 +1,6 @@ import type { OptionsWithUri } from 'request'; +import { ApplicationError } from 'n8n-workflow'; import type { IDataObject, IExecuteFunctions, @@ -50,7 +51,10 @@ export async function venafiApiRequest( errors = errors.map((e: IDataObject) => e.message); // Try to return the error prettier - throw new Error(`Venafi error response [${error.statusCode}]: ${errors.join('|')}`); + throw new ApplicationError( + `Venafi error response [${error.statusCode}]: ${errors.join('|')}`, + { level: 'warning' }, + ); } throw error; } diff --git a/packages/nodes-base/test/nodes/Helpers.ts b/packages/nodes-base/test/nodes/Helpers.ts index 907b56daf..be4e7559c 100644 --- a/packages/nodes-base/test/nodes/Helpers.ts +++ b/packages/nodes-base/test/nodes/Helpers.ts @@ -32,7 +32,7 @@ import type { NodeLoadingDetails, WorkflowTestData, } from 'n8n-workflow'; -import { ICredentialsHelper, NodeHelpers, WorkflowHooks } from 'n8n-workflow'; +import { ApplicationError, ICredentialsHelper, NodeHelpers, WorkflowHooks } from 'n8n-workflow'; import { executeWorkflow } from './ExecuteWorkflow'; import { FAKE_CREDENTIALS_DATA } from './FakeCredentialsMap'; @@ -240,7 +240,9 @@ export function setup(testData: WorkflowTestData[] | WorkflowTestData) { for (const credentialName of credentialNames) { const loadInfo = knownCredentials[credentialName]; if (!loadInfo) { - throw new Error(`Unknown credential type: ${credentialName}`); + throw new ApplicationError(`Unknown credential type: ${credentialName}`, { + level: 'warning', + }); } const sourcePath = loadInfo.sourcePath.replace(/^dist\//, './').replace(/\.js$/, '.ts'); const nodeSourcePath = path.join(baseDir, sourcePath); @@ -251,11 +253,11 @@ export function setup(testData: WorkflowTestData[] | WorkflowTestData) { const nodeNames = nodes.map((n) => n.type); for (const nodeName of nodeNames) { if (!nodeName.startsWith('n8n-nodes-base.')) { - throw new Error(`Unknown node type: ${nodeName}`); + throw new ApplicationError(`Unknown node type: ${nodeName}`, { level: 'warning' }); } const loadInfo = knownNodes[nodeName.replace('n8n-nodes-base.', '')]; if (!loadInfo) { - throw new Error(`Unknown node type: ${nodeName}`); + throw new ApplicationError(`Unknown node type: ${nodeName}`, { level: 'warning' }); } const sourcePath = loadInfo.sourcePath.replace(/^dist\//, './').replace(/\.js$/, '.ts'); const nodeSourcePath = path.join(baseDir, sourcePath); @@ -283,7 +285,7 @@ export function getResultNodeData(result: IRun, testData: WorkflowTestData) { } }); - throw new Error(`Data for node "${nodeName}" is missing!`); + throw new ApplicationError(`Data for node "${nodeName}" is missing!`, { level: 'warning' }); } const resultData = result.data.resultData.runData[nodeName].map((nodeData) => { if (nodeData.data === undefined) { @@ -354,7 +356,7 @@ export const workflowToTests = (workflowFiles: string[]) => { } }); if (workflowData.pinData === undefined) { - throw new Error('Workflow data does not contain pinData'); + throw new ApplicationError('Workflow data does not contain pinData', { level: 'warning' }); } const nodeData = preparePinData(workflowData.pinData); diff --git a/packages/nodes-base/utils/utilities.ts b/packages/nodes-base/utils/utilities.ts index 056e56cbd..ac07ad65d 100644 --- a/packages/nodes-base/utils/utilities.ts +++ b/packages/nodes-base/utils/utilities.ts @@ -6,7 +6,7 @@ import type { IPairedItemData, } from 'n8n-workflow'; -import { jsonParse } from 'n8n-workflow'; +import { ApplicationError, jsonParse } from 'n8n-workflow'; import { isEqual, isNull, merge } from 'lodash'; @@ -90,12 +90,12 @@ export function processJsonInput(jsonData: T, inputName?: string) { try { values = jsonParse(jsonData); } catch (error) { - throw new Error(`Input ${input}must contain a valid JSON`); + throw new ApplicationError(`Input ${input} must contain a valid JSON`, { level: 'warning' }); } } else if (typeof jsonData === 'object') { values = jsonData; } else { - throw new Error(`Input ${input}must contain a valid JSON`); + throw new ApplicationError(`Input ${input} must contain a valid JSON`, { level: 'warning' }); } return values;