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 7ac91eefbb..fe805e4af8 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 6f783a1034..bd3260442b 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 fb43c3df4a..ff1a186248 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 63ea8757f8..44aa041a66 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 ded5215174..eb1a6df226 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 a55e9ff46d..eb7c3f4d4a 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 9c57f9c60a..2b827877e8 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 c89ac64040..85b6eecdaf 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 76a6165911..6d657f6645 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 bdb13b1099..52e0faac60 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 be82c1c980..e4ff44f3e2 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 de19384130..d4dbadf30c 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 fe1983a2aa..18696af581 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 7a22c6e55b..49b648527d 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 2f8767a18e..df82c7e060 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 8a09f8b45b..3e3071cc75 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 657c4d077f..e582627f9a 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 83d5a3deeb..5255ffa392 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 3ea607c1d2..85ebecdd9f 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 ecd7180084..304abe6c38 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 4a01a386cd..5abf51a21e 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 e27b863b42..46bf31dba1 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 edf4f02a62..1f858abcd4 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 e417151454..fd4f3c62bf 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 fab2dc8612..17e3d0bf2d 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 2299e35a31..8c392043b8 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 93fd82fc6e..b4cd4f2ec1 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 f3bb67ce90..19e9c661ad 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 7227b1cca8..353de66b06 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 e7532e30bd..ecb654ceb5 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 907b56dafd..be4e7559ce 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 056e56cbdb..ac07ad65d7 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;