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 <michael.k@radency.com>
This commit is contained in:
Iván Ovejero 2023-12-05 11:17:08 +01:00 committed by GitHub
parent 38b88b946b
commit e77fd5d286
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 164 additions and 86 deletions

View file

@ -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;

View file

@ -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 };

View file

@ -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];

View file

@ -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) {

View file

@ -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',
});
}
}

View file

@ -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' });
}
}

View file

@ -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]");

View file

@ -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' },
);
}

View file

@ -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;
}

View file

@ -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' });
}
}

View file

@ -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' },
);
}
}

View file

@ -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<INodeProper
});
if (responseData.items === undefined) {
throw new Error('No data got returned');
throw new ApplicationError('No data got returned', { level: 'warning' });
}
const returnData: INodePropertyOptions[] = [];
for (const baseData of responseData.items) {
@ -160,7 +160,7 @@ export async function getFields(
const responseData = await apiRequestAllItems.call(this, 'GET', endpoint, {}, 'fields');
if (responseData.items === undefined) {
throw new Error('No form fields meta data got returned');
throw new ApplicationError('No form fields meta data got returned', { level: 'warning' });
}
const fields = responseData.items as IFormstackFieldDefinitionType[];
@ -185,7 +185,7 @@ export async function getSubmission(
const responseData = await apiRequestAllItems.call(this, 'GET', endpoint, {}, 'data');
if (responseData.items === undefined) {
throw new Error('No form fields meta data got returned');
throw new ApplicationError('No form fields meta data got returned', { level: 'warning' });
}
return responseData.items as IFormstackSubmissionFieldContainer[];

View file

@ -5,7 +5,7 @@ import type {
INodeProperties,
} from 'n8n-workflow';
import { NodeOperationError, sleep } from 'n8n-workflow';
import { ApplicationError, NodeOperationError, sleep } from 'n8n-workflow';
import type { ResponseWithJobReference } from '../../helpers/interfaces';
import { prepareOutput } from '../../helpers/utils';
@ -285,10 +285,11 @@ export async function execute(this: IExecuteFunctions): Promise<INodeExecutionDa
}
if ((response?.errors as IDataObject[])?.length) {
const errorMessages = (response.errors as IDataObject[]).map((error) => 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) {

View file

@ -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(

View file

@ -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' },
);
};

View file

@ -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;
}

View file

@ -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') {

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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) {

View file

@ -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<Mysql2Pool> {
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);

View file

@ -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<INodeLi
[schema.value],
);
} catch (error) {
throw new Error(error as string);
throw new ApplicationError(error as string);
}
const results: INodeListSearchItems[] = (tableList as IDataObject[]).map((s) => ({
name: s.table_name as string,

View file

@ -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',
});
}

View file

@ -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)) {

View file

@ -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' });
}
}

View file

@ -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',
});
}
}
}

View file

@ -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' },
);
}
}
}

View file

@ -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' },
);
}
}
}

View file

@ -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',
});
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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<T>(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;