mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 21:07:28 -08:00
refactor(core): Switch plain errors in cli
to ApplicationError
(#7857)
Ensure all errors in `cli` are `ApplicationError` or children of it and contain no variables in the message, to continue normalizing all the errors we report to Sentry Follow-up to: https://github.com/n8n-io/n8n/pull/7839
This commit is contained in:
parent
87def60979
commit
c08c5cc37b
|
@ -7,7 +7,7 @@ import type {
|
|||
IRun,
|
||||
ExecutionStatus,
|
||||
} from 'n8n-workflow';
|
||||
import { WorkflowOperationError, createDeferredPromise } from 'n8n-workflow';
|
||||
import { ApplicationError, WorkflowOperationError, createDeferredPromise } from 'n8n-workflow';
|
||||
|
||||
import type { ChildProcess } from 'child_process';
|
||||
import type PCancelable from 'p-cancelable';
|
||||
|
@ -64,7 +64,7 @@ export class ActiveExecutions {
|
|||
await Container.get(ExecutionRepository).createNewExecution(fullExecutionData);
|
||||
executionId = executionResult.id;
|
||||
if (executionId === undefined) {
|
||||
throw new Error('There was an issue assigning an execution id to the execution');
|
||||
throw new ApplicationError('There was an issue assigning an execution id to the execution');
|
||||
}
|
||||
executionStatus = 'running';
|
||||
} else {
|
||||
|
@ -98,9 +98,9 @@ export class ActiveExecutions {
|
|||
|
||||
attachWorkflowExecution(executionId: string, workflowExecution: PCancelable<IRun>) {
|
||||
if (this.activeExecutions[executionId] === undefined) {
|
||||
throw new Error(
|
||||
`No active execution with id "${executionId}" got found to attach to workflowExecution to!`,
|
||||
);
|
||||
throw new ApplicationError('No active execution found to attach to workflow execution to', {
|
||||
extra: { executionId },
|
||||
});
|
||||
}
|
||||
|
||||
this.activeExecutions[executionId].workflowExecution = workflowExecution;
|
||||
|
@ -111,9 +111,9 @@ export class ActiveExecutions {
|
|||
responsePromise: IDeferredPromise<IExecuteResponsePromiseData>,
|
||||
): void {
|
||||
if (this.activeExecutions[executionId] === undefined) {
|
||||
throw new Error(
|
||||
`No active execution with id "${executionId}" got found to attach to workflowExecution to!`,
|
||||
);
|
||||
throw new ApplicationError('No active execution found to attach to workflow execution to', {
|
||||
extra: { executionId },
|
||||
});
|
||||
}
|
||||
|
||||
this.activeExecutions[executionId].responsePromise = responsePromise;
|
||||
|
|
|
@ -6,7 +6,7 @@ import type {
|
|||
WorkflowActivateMode,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
import { WebhookPathTakenError } from 'n8n-workflow';
|
||||
import { ApplicationError, WebhookPathTakenError } from 'n8n-workflow';
|
||||
import * as NodeExecuteFunctions from 'n8n-core';
|
||||
|
||||
@Service()
|
||||
|
@ -32,7 +32,9 @@ export class ActiveWebhooks {
|
|||
activation: WorkflowActivateMode,
|
||||
): Promise<void> {
|
||||
if (workflow.id === undefined) {
|
||||
throw new Error('Webhooks can only be added for saved workflows as an id is needed!');
|
||||
throw new ApplicationError(
|
||||
'Webhooks can only be added for saved workflows as an ID is needed',
|
||||
);
|
||||
}
|
||||
if (webhookData.path.endsWith('/')) {
|
||||
webhookData.path = webhookData.path.slice(0, -1);
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
WorkflowActivationError,
|
||||
ErrorReporterProxy as ErrorReporter,
|
||||
WebhookPathTakenError,
|
||||
ApplicationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import type express from 'express';
|
||||
|
@ -425,7 +426,7 @@ export class ActiveWorkflowRunner implements IWebhookManager {
|
|||
});
|
||||
|
||||
if (workflowData === null) {
|
||||
throw new Error(`Could not find workflow with id "${workflowId}"`);
|
||||
throw new ApplicationError('Could not find workflow', { extra: { workflowId } });
|
||||
}
|
||||
|
||||
const workflow = new Workflow({
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import { Service } from 'typedi';
|
||||
import { loadClassInIsolation } from 'n8n-core';
|
||||
import type { ICredentialType, ICredentialTypes, LoadedClass } from 'n8n-workflow';
|
||||
import {
|
||||
ApplicationError,
|
||||
type ICredentialType,
|
||||
type ICredentialTypes,
|
||||
type LoadedClass,
|
||||
} from 'n8n-workflow';
|
||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
||||
|
||||
|
@ -46,6 +51,8 @@ export class CredentialTypes implements ICredentialTypes {
|
|||
loadedCredentials[type] = { sourcePath, type: loaded };
|
||||
return loadedCredentials[type];
|
||||
}
|
||||
throw new Error(`${RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL}: ${type}`);
|
||||
throw new ApplicationError(RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL, {
|
||||
tags: { credentialType: type },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import {
|
|||
RoutingNode,
|
||||
Workflow,
|
||||
ErrorReporterProxy as ErrorReporter,
|
||||
ApplicationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import type { ICredentialsDb } from '@/Interfaces';
|
||||
|
@ -81,7 +82,9 @@ const mockNodeTypes: INodeTypes = {
|
|||
},
|
||||
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
||||
if (!mockNodesData[nodeType]) {
|
||||
throw new Error(`${RESPONSE_ERROR_MESSAGES.NO_NODE}: ${nodeType}`);
|
||||
throw new ApplicationError(RESPONSE_ERROR_MESSAGES.NO_NODE, {
|
||||
tags: { nodeType },
|
||||
});
|
||||
}
|
||||
return NodeHelpers.getVersionedNodeType(mockNodesData[nodeType].type, version);
|
||||
},
|
||||
|
@ -258,7 +261,10 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
userId?: string,
|
||||
): Promise<Credentials> {
|
||||
if (!nodeCredential.id) {
|
||||
throw new Error(`Credential "${nodeCredential.name}" of type "${type}" has no ID.`);
|
||||
throw new ApplicationError('Found credential with no ID.', {
|
||||
extra: { credentialName: nodeCredential.name },
|
||||
tags: { credentialType: type },
|
||||
});
|
||||
}
|
||||
|
||||
let credential: CredentialsEntity;
|
||||
|
@ -291,7 +297,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
const credentialTypeData = this.credentialTypes.getByName(type);
|
||||
|
||||
if (credentialTypeData === undefined) {
|
||||
throw new Error(`The credentials of type "${type}" are not known.`);
|
||||
throw new ApplicationError('Unknown credential type', { tags: { credentialType: type } });
|
||||
}
|
||||
|
||||
if (credentialTypeData.extends === undefined) {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Container } from 'typedi';
|
|||
import type { DataSourceOptions as ConnectionOptions, EntityManager, LoggerOptions } from 'typeorm';
|
||||
import { DataSource as Connection } from 'typeorm';
|
||||
import type { TlsOptions } from 'tls';
|
||||
import { ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
|
||||
import { ApplicationError, ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
|
||||
|
||||
import config from '@/config';
|
||||
|
||||
|
@ -93,7 +93,7 @@ export function getConnectionOptions(dbType: DatabaseType): ConnectionOptions {
|
|||
return getSqliteConnectionOptions();
|
||||
|
||||
default:
|
||||
throw new Error(`The database "${dbType}" is currently not supported!`);
|
||||
throw new ApplicationError('Database type currently not supported', { extra: { dbType } });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import { UserRepository } from '@db/repositories/user.repository';
|
|||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
import { SettingsRepository } from '@db/repositories/settings.repository';
|
||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
@Service()
|
||||
export class ExternalHooks implements IExternalHooksClass {
|
||||
|
@ -71,12 +72,13 @@ export class ExternalHooks implements IExternalHooksClass {
|
|||
|
||||
const hookFile = require(hookFilePath) as IExternalHooksFileData;
|
||||
this.loadHooks(hookFile);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
`Problem loading external hook file "${hookFilePath}": ${error.message}`,
|
||||
{ cause: error as Error },
|
||||
);
|
||||
} catch (e) {
|
||||
const error = e instanceof Error ? e : new Error(`${e}`);
|
||||
|
||||
throw new ApplicationError('Problem loading external hook file', {
|
||||
extra: { errorMessage: error.message, hookFilePath },
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import Container, { Service } from 'typedi';
|
|||
|
||||
import { Logger } from '@/Logger';
|
||||
|
||||
import { jsonParse, type IDataObject } from 'n8n-workflow';
|
||||
import { jsonParse, type IDataObject, ApplicationError } from 'n8n-workflow';
|
||||
import {
|
||||
EXTERNAL_SECRETS_INITIAL_BACKOFF,
|
||||
EXTERNAL_SECRETS_MAX_BACKOFF,
|
||||
|
@ -90,7 +90,7 @@ export class ExternalSecretsManager {
|
|||
try {
|
||||
return jsonParse(decryptedData);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'External Secrets Settings could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data.',
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { SecretsProvider, SecretsProviderSettings, SecretsProviderState } f
|
|||
import InfisicalClient from 'infisical-node';
|
||||
import { populateClientWorkspaceConfigsHelper } from 'infisical-node/lib/helpers/key';
|
||||
import { getServiceTokenData } from 'infisical-node/lib/api/serviceTokenData';
|
||||
import type { IDataObject, INodeProperties } from 'n8n-workflow';
|
||||
import { ApplicationError, type IDataObject, type INodeProperties } from 'n8n-workflow';
|
||||
import { EXTERNAL_SECRETS_NAME_REGEX } from '../constants';
|
||||
|
||||
export interface InfisicalSettings {
|
||||
|
@ -74,10 +74,10 @@ export class InfisicalProvider implements SecretsProvider {
|
|||
|
||||
async update(): Promise<void> {
|
||||
if (!this.client) {
|
||||
throw new Error('Updated attempted on Infisical when initialization failed');
|
||||
throw new ApplicationError('Updated attempted on Infisical when initialization failed');
|
||||
}
|
||||
if (!(await this.test())[0]) {
|
||||
throw new Error('Infisical provider test failed during update');
|
||||
throw new ApplicationError('Infisical provider test failed during update');
|
||||
}
|
||||
const secrets = (await this.client.getAllSecrets({
|
||||
environment: this.environment,
|
||||
|
@ -120,7 +120,7 @@ export class InfisicalProvider implements SecretsProvider {
|
|||
if (serviceTokenData.scopes) {
|
||||
return serviceTokenData.scopes[0].environment;
|
||||
}
|
||||
throw new Error("Couldn't find environment for Infisical");
|
||||
throw new ApplicationError("Couldn't find environment for Infisical");
|
||||
}
|
||||
|
||||
async test(): Promise<[boolean] | [boolean, string]> {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { ApplicationError } from 'n8n-workflow';
|
||||
import { LdapService } from './LdapService.ee';
|
||||
import { LdapSync } from './LdapSync.ee';
|
||||
import type { LdapConfig } from './types';
|
||||
|
@ -15,7 +16,7 @@ export class LdapManager {
|
|||
sync: LdapSync;
|
||||
} {
|
||||
if (!this.initialized) {
|
||||
throw new Error('LDAP Manager has not been initialized');
|
||||
throw new ApplicationError('LDAP Manager has not been initialized');
|
||||
}
|
||||
return this.ldap;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import type { LdapConfig } from './types';
|
|||
import { formatUrl, getMappingAttributes } from './helpers';
|
||||
import { BINARY_AD_ATTRIBUTES } from './constants';
|
||||
import type { ConnectionOptions } from 'tls';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export class LdapService {
|
||||
private client: Client | undefined;
|
||||
|
@ -25,7 +26,7 @@ export class LdapService {
|
|||
*/
|
||||
private async getClient() {
|
||||
if (this._config === undefined) {
|
||||
throw new Error('Service cannot be used without setting the property config');
|
||||
throw new ApplicationError('Service cannot be used without setting the property config');
|
||||
}
|
||||
if (this.client === undefined) {
|
||||
const url = formatUrl(
|
||||
|
|
|
@ -17,6 +17,7 @@ import type { RunningMode, SyncStatus } from '@db/entities/AuthProviderSyncHisto
|
|||
import { Container } from 'typedi';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { Logger } from '@/Logger';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export class LdapSync {
|
||||
private intervalId: NodeJS.Timeout | undefined = undefined;
|
||||
|
@ -64,7 +65,7 @@ export class LdapSync {
|
|||
*/
|
||||
scheduleRun(): void {
|
||||
if (!this._config.synchronizationInterval) {
|
||||
throw new Error('Interval variable has to be defined');
|
||||
throw new ApplicationError('Interval variable has to be defined');
|
||||
}
|
||||
this.intervalId = setInterval(async () => {
|
||||
await this.run('live');
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
LDAP_LOGIN_LABEL,
|
||||
} from './constants';
|
||||
import type { ConnectionSecurity, LdapConfig } from './types';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse } from 'n8n-workflow';
|
||||
import { License } from '@/License';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import {
|
||||
|
@ -157,7 +157,7 @@ export const updateLdapConfig = async (ldapConfig: LdapConfig): Promise<void> =>
|
|||
const { valid, message } = validateLdapConfigurationSchema(ldapConfig);
|
||||
|
||||
if (!valid) {
|
||||
throw new Error(message);
|
||||
throw new ApplicationError(message);
|
||||
}
|
||||
|
||||
if (ldapConfig.loginEnabled && getCurrentAuthenticationMethod() === 'saml') {
|
||||
|
|
|
@ -17,7 +17,7 @@ import type {
|
|||
INodeTypeData,
|
||||
ICredentialTypeData,
|
||||
} from 'n8n-workflow';
|
||||
import { ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
|
||||
import { ApplicationError, ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
|
||||
|
||||
import config from '@/config';
|
||||
import {
|
||||
|
@ -56,7 +56,7 @@ export class LoadNodesAndCredentials {
|
|||
) {}
|
||||
|
||||
async init() {
|
||||
if (inTest) throw new Error('Not available in tests');
|
||||
if (inTest) throw new ApplicationError('Not available in tests');
|
||||
|
||||
// Make sure the imported modules can resolve dependencies fine.
|
||||
const delimiter = process.platform === 'win32' ? ';' : ':';
|
||||
|
|
|
@ -6,7 +6,7 @@ import type {
|
|||
IVersionedNodeType,
|
||||
LoadedClass,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeHelpers } from 'n8n-workflow';
|
||||
import { ApplicationError, NodeHelpers } from 'n8n-workflow';
|
||||
import { Service } from 'typedi';
|
||||
import { LoadNodesAndCredentials } from './LoadNodesAndCredentials';
|
||||
import { join, dirname } from 'path';
|
||||
|
@ -30,7 +30,7 @@ export class NodeTypes implements INodeTypes {
|
|||
const nodeType = this.getNode(nodeTypeName);
|
||||
|
||||
if (!nodeType) {
|
||||
throw new Error(`Unknown node type: ${nodeTypeName}`);
|
||||
throw new ApplicationError('Unknown node type', { tags: { nodeTypeName } });
|
||||
}
|
||||
|
||||
const { description } = NodeHelpers.getVersionedNodeType(nodeType.type, version);
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import type Bull from 'bull';
|
||||
import { Service } from 'typedi';
|
||||
import type { ExecutionError, IExecuteResponsePromiseData } from 'n8n-workflow';
|
||||
import {
|
||||
ApplicationError,
|
||||
type ExecutionError,
|
||||
type IExecuteResponsePromiseData,
|
||||
} from 'n8n-workflow';
|
||||
import { ActiveExecutions } from '@/ActiveExecutions';
|
||||
import { decodeWebhookResponse } from '@/helpers/decodeWebhookResponse';
|
||||
|
||||
|
@ -96,7 +100,7 @@ export class Queue {
|
|||
getBullObjectInstance(): JobQueue {
|
||||
if (this.jobQueue === undefined) {
|
||||
// if queue is not initialized yet throw an error, since we do not want to hand around an undefined queue
|
||||
throw new Error('Queue is not initialized yet!');
|
||||
throw new ApplicationError('Queue is not initialized yet!');
|
||||
}
|
||||
return this.jobQueue;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import type {
|
|||
IExecutionsSummary,
|
||||
IN8nUISettings,
|
||||
} from 'n8n-workflow';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse } from 'n8n-workflow';
|
||||
|
||||
// @ts-ignore
|
||||
import timezones from 'google-timezones-json';
|
||||
|
@ -672,7 +672,9 @@ export class Server extends AbstractServer {
|
|||
const job = currentJobs.find((job) => job.data.executionId === req.params.id);
|
||||
|
||||
if (!job) {
|
||||
throw new Error(`Could not stop "${req.params.id}" as it is no longer in queue.`);
|
||||
throw new ApplicationError('Could not stop job because it is no longer in queue.', {
|
||||
extra: { jobId: req.params.id },
|
||||
});
|
||||
} else {
|
||||
await queue.stopJob(job);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import type express from 'express';
|
||||
import { Service } from 'typedi';
|
||||
|
||||
import type {
|
||||
IWebhookData,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
IHttpRequestMethods,
|
||||
Workflow,
|
||||
WorkflowActivateMode,
|
||||
WorkflowExecuteMode,
|
||||
import {
|
||||
type IWebhookData,
|
||||
type IWorkflowExecuteAdditionalData,
|
||||
type IHttpRequestMethods,
|
||||
type Workflow,
|
||||
type WorkflowActivateMode,
|
||||
type WorkflowExecuteMode,
|
||||
ApplicationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { ActiveWebhooks } from '@/ActiveWebhooks';
|
||||
|
@ -215,7 +216,9 @@ export class TestWebhooks implements IWebhookManager {
|
|||
}
|
||||
|
||||
if (workflow.id === undefined) {
|
||||
throw new Error('Webhooks can only be added for saved workflows as an id is needed!');
|
||||
throw new ApplicationError(
|
||||
'Webhooks can only be added for saved workflows as an ID is needed',
|
||||
);
|
||||
}
|
||||
|
||||
// Remove test-webhooks automatically if they do not get called (after 120 seconds)
|
||||
|
|
|
@ -11,6 +11,7 @@ import { getWebhookBaseUrl } from '@/WebhookHelpers';
|
|||
import { RoleService } from '@/services/role.service';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export function isSharingEnabled(): boolean {
|
||||
return Container.get(License).isSharingEnabled();
|
||||
|
@ -94,14 +95,15 @@ export const hashPassword = async (validPassword: string): Promise<string> =>
|
|||
export async function compareHash(plaintext: string, hashed: string): Promise<boolean | undefined> {
|
||||
try {
|
||||
return await compare(plaintext, hashed);
|
||||
} catch (error) {
|
||||
} catch (e) {
|
||||
const error = e instanceof Error ? e : new Error(`${e}`);
|
||||
|
||||
if (error instanceof Error && error.message.includes('Invalid salt version')) {
|
||||
error.message +=
|
||||
'. Comparison against unhashed string. Please check that the value compared against has been hashed.';
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
throw new Error(error);
|
||||
throw new ApplicationError(error.message, { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import { Container, Service } from 'typedi';
|
|||
import config from '@/config';
|
||||
import type { InviteEmailData, PasswordResetData, SendEmailResult } from './Interfaces';
|
||||
import { NodeMailer } from './NodeMailer';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
type Template = HandlebarsTemplateDelegate<unknown>;
|
||||
type TemplateName = 'invite' | 'passwordReset';
|
||||
|
@ -50,7 +51,7 @@ export class UserManagementMailer {
|
|||
}
|
||||
|
||||
async verifyConnection(): Promise<void> {
|
||||
if (!this.mailer) throw new Error('No mailer configured.');
|
||||
if (!this.mailer) throw new ApplicationError('No mailer configured.');
|
||||
|
||||
return this.mailer.verifyConnection();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import { ErrorReporterProxy as ErrorReporter, WorkflowOperationError } from 'n8n-workflow';
|
||||
import {
|
||||
ApplicationError,
|
||||
ErrorReporterProxy as ErrorReporter,
|
||||
WorkflowOperationError,
|
||||
} from 'n8n-workflow';
|
||||
import { Container, Service } from 'typedi';
|
||||
import type { FindManyOptions, ObjectLiteral } from 'typeorm';
|
||||
import { Not, LessThanOrEqual } from 'typeorm';
|
||||
|
@ -106,7 +110,9 @@ export class WaitTracker {
|
|||
});
|
||||
|
||||
if (!execution) {
|
||||
throw new Error(`The execution ID "${executionId}" could not be found.`);
|
||||
throw new ApplicationError('Execution not found.', {
|
||||
extra: { executionId },
|
||||
});
|
||||
}
|
||||
|
||||
if (!['new', 'unknown', 'waiting', 'running'].includes(execution.status)) {
|
||||
|
@ -129,7 +135,9 @@ export class WaitTracker {
|
|||
},
|
||||
);
|
||||
if (!restoredExecution) {
|
||||
throw new Error(`Execution ${executionId} could not be recovered or canceled.`);
|
||||
throw new ApplicationError('Execution could not be recovered or canceled.', {
|
||||
extra: { executionId },
|
||||
});
|
||||
}
|
||||
fullExecutionData = restoredExecution;
|
||||
}
|
||||
|
@ -172,14 +180,14 @@ export class WaitTracker {
|
|||
});
|
||||
|
||||
if (!fullExecutionData) {
|
||||
throw new Error(`The execution with the id "${executionId}" does not exist.`);
|
||||
throw new ApplicationError('Execution does not exist.', { extra: { executionId } });
|
||||
}
|
||||
if (fullExecutionData.finished) {
|
||||
throw new Error('The execution did succeed and can so not be started again.');
|
||||
throw new ApplicationError('The execution did succeed and can so not be started again.');
|
||||
}
|
||||
|
||||
if (!fullExecutionData.workflowData.id) {
|
||||
throw new Error('Only saved workflows can be resumed.');
|
||||
throw new ApplicationError('Only saved workflows can be resumed.');
|
||||
}
|
||||
const workflowId = fullExecutionData.workflowData.id;
|
||||
const user = await this.ownershipService.getWorkflowOwnerCached(workflowId);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Container from 'typedi';
|
||||
import type { INode, IWorkflowCredentials } from 'n8n-workflow';
|
||||
import { ApplicationError, type INode, type IWorkflowCredentials } from 'n8n-workflow';
|
||||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
|
@ -24,8 +24,9 @@ export async function WorkflowCredentials(nodes: INode[]): Promise<IWorkflowCred
|
|||
nodeCredentials = node.credentials[type];
|
||||
|
||||
if (!nodeCredentials.id) {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
`Credentials with name "${nodeCredentials.name}" for type "${type}" miss an ID.`,
|
||||
{ extra: { credentialName: nodeCredentials.name }, tags: { credentialType: type } },
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -35,9 +36,10 @@ export async function WorkflowCredentials(nodes: INode[]): Promise<IWorkflowCred
|
|||
type,
|
||||
});
|
||||
if (!foundCredentials) {
|
||||
throw new Error(
|
||||
`Could not find credentials for type "${type}" with ID "${nodeCredentials.id}".`,
|
||||
);
|
||||
throw new ApplicationError('Could not find credential.', {
|
||||
tags: { credentialType: type },
|
||||
extra: { credentialId: nodeCredentials.id },
|
||||
});
|
||||
}
|
||||
|
||||
returnCredentials[type][nodeCredentials.id] = foundCredentials;
|
||||
|
|
|
@ -26,6 +26,7 @@ import type {
|
|||
ExecutionError,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
ApplicationError,
|
||||
ErrorReporterProxy as ErrorReporter,
|
||||
NodeOperationError,
|
||||
Workflow,
|
||||
|
@ -679,7 +680,7 @@ export async function getWorkflowData(
|
|||
parentWorkflowSettings?: IWorkflowSettings,
|
||||
): Promise<IWorkflowBase> {
|
||||
if (workflowInfo.id === undefined && workflowInfo.code === undefined) {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'No information about the workflow to execute found. Please provide either the "id" or "code"!',
|
||||
);
|
||||
}
|
||||
|
@ -691,7 +692,9 @@ export async function getWorkflowData(
|
|||
workflowData = await WorkflowsService.get({ id: workflowInfo.id }, { relations });
|
||||
|
||||
if (workflowData === undefined || workflowData === null) {
|
||||
throw new Error(`The workflow with the id "${workflowInfo.id}" does not exist.`);
|
||||
throw new ApplicationError('Workflow does not exist.', {
|
||||
extra: { workflowId: workflowInfo.id },
|
||||
});
|
||||
}
|
||||
} else {
|
||||
workflowData = workflowInfo.code ?? null;
|
||||
|
|
|
@ -10,6 +10,7 @@ import { UserRepository } from '@db/repositories/user.repository';
|
|||
import { JwtService } from '@/services/jwt.service';
|
||||
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
|
||||
import { AuthError } from '@/errors/response-errors/auth.error';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export function issueJWT(user: User): JwtToken {
|
||||
const { id, email, password } = user;
|
||||
|
@ -70,7 +71,7 @@ export async function resolveJwtContent(jwtPayload: JwtPayload): Promise<User> {
|
|||
if (!user || jwtPayload.password !== passwordHash || user.email !== jwtPayload.email) {
|
||||
// When owner hasn't been set up, the default user
|
||||
// won't have email nor password (both equals null)
|
||||
throw new Error('Invalid token content');
|
||||
throw new ApplicationError('Invalid token content');
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'reflect-metadata';
|
|||
import { Command } from '@oclif/command';
|
||||
import { ExitError } from '@oclif/errors';
|
||||
import { Container } from 'typedi';
|
||||
import { ErrorReporterProxy as ErrorReporter, sleep } from 'n8n-workflow';
|
||||
import { ApplicationError, ErrorReporterProxy as ErrorReporter, sleep } from 'n8n-workflow';
|
||||
import { BinaryDataService, InstanceSettings, ObjectStoreService } from 'n8n-core';
|
||||
import type { AbstractServer } from '@/AbstractServer';
|
||||
import { Logger } from '@/Logger';
|
||||
|
@ -127,7 +127,7 @@ export abstract class BaseCommand extends Command {
|
|||
if (!isSelected && !isAvailable) return;
|
||||
|
||||
if (isSelected && !isAvailable) {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'External storage selected but unavailable. Please make external storage available by adding "s3" to `N8N_AVAILABLE_BINARY_DATA_MODES`.',
|
||||
);
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ export abstract class BaseCommand extends Command {
|
|||
const host = config.getEnv('externalStorage.s3.host');
|
||||
|
||||
if (host === '') {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'External storage host not configured. Please set `N8N_EXTERNAL_STORAGE_S3_HOST`.',
|
||||
);
|
||||
}
|
||||
|
@ -182,13 +182,13 @@ export abstract class BaseCommand extends Command {
|
|||
};
|
||||
|
||||
if (bucket.name === '') {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'External storage bucket name not configured. Please set `N8N_EXTERNAL_STORAGE_S3_BUCKET_NAME`.',
|
||||
);
|
||||
}
|
||||
|
||||
if (bucket.region === '') {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'External storage bucket region not configured. Please set `N8N_EXTERNAL_STORAGE_S3_BUCKET_REGION`.',
|
||||
);
|
||||
}
|
||||
|
@ -199,13 +199,13 @@ export abstract class BaseCommand extends Command {
|
|||
};
|
||||
|
||||
if (credentials.accessKey === '') {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'External storage access key not configured. Please set `N8N_EXTERNAL_STORAGE_S3_ACCESS_KEY`.',
|
||||
);
|
||||
}
|
||||
|
||||
if (credentials.accessSecret === '') {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'External storage access secret not configured. Please set `N8N_EXTERNAL_STORAGE_S3_ACCESS_SECRET`.',
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import type { Risk } from '@/security-audit/types';
|
|||
import { BaseCommand } from './BaseCommand';
|
||||
import { Container } from 'typedi';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export class SecurityAudit extends BaseCommand {
|
||||
static description = 'Generate a security audit report for this n8n instance';
|
||||
|
@ -46,7 +47,7 @@ export class SecurityAudit extends BaseCommand {
|
|||
|
||||
const hint = `Valid categories are: ${RISK_CATEGORIES.join(', ')}`;
|
||||
|
||||
throw new Error([message, hint].join('. '));
|
||||
throw new ApplicationError([message, hint].join('. '));
|
||||
}
|
||||
|
||||
const result = await Container.get(SecurityAuditService).run(
|
||||
|
|
|
@ -2,7 +2,7 @@ import { promises as fs } from 'fs';
|
|||
import { flags } from '@oclif/command';
|
||||
import { PLACEHOLDER_EMPTY_WORKFLOW_ID } from 'n8n-core';
|
||||
import type { IWorkflowBase } from 'n8n-workflow';
|
||||
import { ExecutionBaseError } from 'n8n-workflow';
|
||||
import { ApplicationError, ExecutionBaseError } from 'n8n-workflow';
|
||||
|
||||
import { ActiveExecutions } from '@/ActiveExecutions';
|
||||
import { WorkflowRunner } from '@/WorkflowRunner';
|
||||
|
@ -89,7 +89,7 @@ export class Execute extends BaseCommand {
|
|||
}
|
||||
|
||||
if (!workflowData) {
|
||||
throw new Error('Failed to retrieve workflow data for requested workflow');
|
||||
throw new ApplicationError('Failed to retrieve workflow data for requested workflow');
|
||||
}
|
||||
|
||||
if (!isWorkflowIdValid(workflowId)) {
|
||||
|
@ -113,7 +113,7 @@ export class Execute extends BaseCommand {
|
|||
const data = await activeExecutions.getPostExecutePromise(executionId);
|
||||
|
||||
if (data === undefined) {
|
||||
throw new Error('Workflow did not return any data!');
|
||||
throw new ApplicationError('Workflow did not return any data');
|
||||
}
|
||||
|
||||
if (data.data.resultData.error) {
|
||||
|
|
|
@ -3,7 +3,7 @@ import fs from 'fs';
|
|||
import os from 'os';
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IRun, ITaskData } from 'n8n-workflow';
|
||||
import { jsonParse, sleep } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse, sleep } from 'n8n-workflow';
|
||||
import { sep } from 'path';
|
||||
import { diff } from 'json-diff';
|
||||
import pick from 'lodash/pick';
|
||||
|
@ -486,7 +486,7 @@ export class ExecuteBatch extends BaseCommand {
|
|||
this.updateStatus();
|
||||
}
|
||||
} else {
|
||||
throw new Error('Wrong execution status - cannot proceed');
|
||||
throw new ApplicationError('Wrong execution status - cannot proceed');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import type { ICredentialsDb, ICredentialsDecryptedDb } from '@/Interfaces';
|
|||
import { BaseCommand } from '../BaseCommand';
|
||||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
import Container from 'typedi';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export class ExportCredentialsCommand extends BaseCommand {
|
||||
static description = 'Export credentials';
|
||||
|
@ -125,7 +126,7 @@ export class ExportCredentialsCommand extends BaseCommand {
|
|||
}
|
||||
|
||||
if (credentials.length === 0) {
|
||||
throw new Error('No credentials found with specified filters.');
|
||||
throw new ApplicationError('No credentials found with specified filters');
|
||||
}
|
||||
|
||||
if (flags.separate) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
|||
import { BaseCommand } from '../BaseCommand';
|
||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||
import Container from 'typedi';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export class ExportWorkflowsCommand extends BaseCommand {
|
||||
static description = 'Export workflows';
|
||||
|
@ -111,7 +112,7 @@ export class ExportWorkflowsCommand extends BaseCommand {
|
|||
});
|
||||
|
||||
if (workflows.length === 0) {
|
||||
throw new Error('No workflows found with specified filters.');
|
||||
throw new ApplicationError('No workflows found with specified filters');
|
||||
}
|
||||
|
||||
if (flags.separate) {
|
||||
|
|
|
@ -12,7 +12,7 @@ import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
|||
import { disableAutoGeneratedIds } from '@db/utils/commandHelpers';
|
||||
import { BaseCommand } from '../BaseCommand';
|
||||
import type { ICredentialsEncrypted } from 'n8n-workflow';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse } from 'n8n-workflow';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { UM_FIX_INSTRUCTION } from '@/constants';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
|
@ -113,7 +113,7 @@ export class ImportCredentialsCommand extends BaseCommand {
|
|||
totalImported = credentials.length;
|
||||
|
||||
if (!Array.isArray(credentials)) {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'File does not seem to contain credentials. Make sure the credentials are contained in an array.',
|
||||
);
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ export class ImportCredentialsCommand extends BaseCommand {
|
|||
const ownerCredentialRole = await Container.get(RoleService).findCredentialOwnerRole();
|
||||
|
||||
if (!ownerCredentialRole) {
|
||||
throw new Error(`Failed to find owner credential role. ${UM_FIX_INSTRUCTION}`);
|
||||
throw new ApplicationError(`Failed to find owner credential role. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
this.ownerCredentialRole = ownerCredentialRole;
|
||||
|
@ -179,7 +179,7 @@ export class ImportCredentialsCommand extends BaseCommand {
|
|||
(await Container.get(UserRepository).findOneBy({ globalRoleId: ownerGlobalRole.id }));
|
||||
|
||||
if (!owner) {
|
||||
throw new Error(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
return owner;
|
||||
|
@ -189,7 +189,7 @@ export class ImportCredentialsCommand extends BaseCommand {
|
|||
const user = await Container.get(UserRepository).findOneBy({ id: userId });
|
||||
|
||||
if (!user) {
|
||||
throw new Error(`Failed to find user with ID ${userId}`);
|
||||
throw new ApplicationError('Failed to find user', { extra: { userId } });
|
||||
}
|
||||
|
||||
return user;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { flags } from '@oclif/command';
|
||||
import type { INode, INodeCredentialsDetails } from 'n8n-workflow';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse } from 'n8n-workflow';
|
||||
import fs from 'fs';
|
||||
import glob from 'fast-glob';
|
||||
import { Container } from 'typedi';
|
||||
|
@ -24,7 +24,7 @@ import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
|||
|
||||
function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IWorkflowToImport[] {
|
||||
if (!Array.isArray(workflows)) {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'File does not seem to contain workflows. Make sure the workflows are contained in an array.',
|
||||
);
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IW
|
|||
!Object.prototype.hasOwnProperty.call(workflow, 'nodes') ||
|
||||
!Object.prototype.hasOwnProperty.call(workflow, 'connections')
|
||||
) {
|
||||
throw new Error('File does not seem to contain valid workflows.');
|
||||
throw new ApplicationError('File does not seem to contain valid workflows.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,7 +217,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
|
|||
const ownerWorkflowRole = await Container.get(RoleService).findWorkflowOwnerRole();
|
||||
|
||||
if (!ownerWorkflowRole) {
|
||||
throw new Error(`Failed to find owner workflow role. ${UM_FIX_INSTRUCTION}`);
|
||||
throw new ApplicationError(`Failed to find owner workflow role. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
this.ownerWorkflowRole = ownerWorkflowRole;
|
||||
|
@ -244,7 +244,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
|
|||
(await Container.get(UserRepository).findOneBy({ globalRoleId: ownerGlobalRole?.id }));
|
||||
|
||||
if (!owner) {
|
||||
throw new Error(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
return owner;
|
||||
|
@ -254,7 +254,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
|
|||
const user = await Container.get(UserRepository).findOneBy({ id: userId });
|
||||
|
||||
if (!user) {
|
||||
throw new Error(`Failed to find user with ID ${userId}`);
|
||||
throw new ApplicationError('Failed to find user', { extra: { userId } });
|
||||
}
|
||||
|
||||
return user;
|
||||
|
|
|
@ -13,7 +13,7 @@ import type {
|
|||
INodeTypes,
|
||||
IRun,
|
||||
} from 'n8n-workflow';
|
||||
import { Workflow, NodeOperationError, sleep } from 'n8n-workflow';
|
||||
import { Workflow, NodeOperationError, sleep, ApplicationError } from 'n8n-workflow';
|
||||
|
||||
import * as Db from '@/Db';
|
||||
import * as ResponseHelper from '@/ResponseHelper';
|
||||
|
@ -125,8 +125,9 @@ export class Worker extends BaseCommand {
|
|||
`Worker failed to find data of execution "${executionId}" in database. Cannot continue.`,
|
||||
{ executionId },
|
||||
);
|
||||
throw new Error(
|
||||
`Unable to find data of execution "${executionId}" in database. Aborting execution.`,
|
||||
throw new ApplicationError(
|
||||
'Unable to find data of execution in database. Aborting execution.',
|
||||
{ extra: { executionId } },
|
||||
);
|
||||
}
|
||||
const workflowId = fullExecutionData.workflowData.id!;
|
||||
|
@ -150,7 +151,7 @@ export class Worker extends BaseCommand {
|
|||
'Worker execution failed because workflow could not be found in database.',
|
||||
{ workflowId, executionId },
|
||||
);
|
||||
throw new Error(`The workflow with the ID "${workflowId}" could not be found`);
|
||||
throw new ApplicationError('Workflow could not be found', { extra: { workflowId } });
|
||||
}
|
||||
staticData = workflowData.staticData;
|
||||
}
|
||||
|
@ -408,7 +409,7 @@ export class Worker extends BaseCommand {
|
|||
try {
|
||||
if (!connection.isInitialized) {
|
||||
// Connection is not active
|
||||
throw new Error('No active database connection!');
|
||||
throw new ApplicationError('No active database connection');
|
||||
}
|
||||
// DB ping
|
||||
await connection.query('SELECT 1');
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import convict from 'convict';
|
||||
import dotenv from 'dotenv';
|
||||
import { readFileSync } from 'fs';
|
||||
import { setGlobalState } from 'n8n-workflow';
|
||||
import { ApplicationError, setGlobalState } from 'n8n-workflow';
|
||||
import { inTest, inE2ETests } from '@/constants';
|
||||
|
||||
if (inE2ETests) {
|
||||
|
@ -53,7 +53,7 @@ if (!inE2ETests && !inTest) {
|
|||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (error.code === 'ENOENT') {
|
||||
throw new Error(`The file "${fileName}" could not be found.`);
|
||||
throw new ApplicationError('File not found', { extra: { fileName } });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { NotStringArrayError } from '@/errors/not-string-array.error';
|
||||
import type { SchemaObj } from 'convict';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export const ensureStringArray = (values: string[], { env }: SchemaObj<string>) => {
|
||||
if (!env) throw new Error(`Missing env: ${env}`);
|
||||
if (!env) throw new ApplicationError('Missing env', { extra: { env } });
|
||||
|
||||
if (!Array.isArray(values)) throw new NotStringArrayError(env);
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import { AuthError } from '@/errors/response-errors/auth.error';
|
|||
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
@Service()
|
||||
@RestController()
|
||||
|
@ -44,8 +45,8 @@ export class AuthController {
|
|||
@Post('/login')
|
||||
async login(req: LoginRequest, res: Response): Promise<PublicUser | undefined> {
|
||||
const { email, password, mfaToken, mfaRecoveryCode } = req.body;
|
||||
if (!email) throw new Error('Email is required to log in');
|
||||
if (!password) throw new Error('Password is required to log in');
|
||||
if (!email) throw new ApplicationError('Email is required to log in');
|
||||
if (!password) throw new ApplicationError('Password is required to log in');
|
||||
|
||||
let user: User | undefined;
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import omit from 'lodash/omit';
|
|||
import set from 'lodash/set';
|
||||
import split from 'lodash/split';
|
||||
import type { OAuth2GrantType } from 'n8n-workflow';
|
||||
import { jsonParse, jsonStringify } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse, jsonStringify } from 'n8n-workflow';
|
||||
import { Authorized, Get, RestController } from '@/decorators';
|
||||
import { OAuthRequest } from '@/requests';
|
||||
import { AbstractOAuthController } from './abstractOAuth.controller';
|
||||
|
@ -255,7 +255,7 @@ export class OAuth2CredentialController extends AbstractOAuthController {
|
|||
errorMessage,
|
||||
});
|
||||
if (typeof decoded.cid !== 'string' || typeof decoded.token !== 'string') {
|
||||
throw new Error(errorMessage);
|
||||
throw new ApplicationError(errorMessage);
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { TableForeignKeyOptions, TableIndexOptions, QueryRunner } from 'typ
|
|||
import { Table, TableColumn } from 'typeorm';
|
||||
import LazyPromise from 'p-lazy';
|
||||
import { Column } from './Column';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
abstract class TableOperation<R = void> extends LazyPromise<R> {
|
||||
abstract execute(queryRunner: QueryRunner): Promise<R>;
|
||||
|
@ -131,7 +132,7 @@ class ModifyNotNull extends TableOperation {
|
|||
async execute(queryRunner: QueryRunner) {
|
||||
const { tableName, prefix, columnName, isNullable } = this;
|
||||
const table = await queryRunner.getTable(`${prefix}${tableName}`);
|
||||
if (!table) throw new Error(`No table found with the name ${tableName}`);
|
||||
if (!table) throw new ApplicationError('No table found', { extra: { tableName } });
|
||||
const oldColumn = table.findColumnByName(columnName)!;
|
||||
const newColumn = oldColumn.clone();
|
||||
newColumn.isNullable = isNullable;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { MigrationContext, ReversibleMigration } from '@db/types';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export class AddGlobalAdminRole1700571993961 implements ReversibleMigration {
|
||||
async up({ escape, runQuery }: MigrationContext) {
|
||||
|
@ -39,7 +40,7 @@ export class AddGlobalAdminRole1700571993961 implements ReversibleMigration {
|
|||
|
||||
const memberRoleId = memberRoleIdResult[0]?.id;
|
||||
if (!memberRoleId) {
|
||||
throw new Error('Could not find global member role!');
|
||||
throw new ApplicationError('Could not find global member role!');
|
||||
}
|
||||
|
||||
await runQuery(
|
||||
|
|
|
@ -8,7 +8,12 @@ import type {
|
|||
SelectQueryBuilder,
|
||||
} from 'typeorm';
|
||||
import { parse, stringify } from 'flatted';
|
||||
import type { ExecutionStatus, IExecutionsSummary, IRunExecutionData } from 'n8n-workflow';
|
||||
import {
|
||||
ApplicationError,
|
||||
type ExecutionStatus,
|
||||
type IExecutionsSummary,
|
||||
type IRunExecutionData,
|
||||
} from 'n8n-workflow';
|
||||
import { BinaryDataService } from 'n8n-core';
|
||||
import type {
|
||||
ExecutionPayload,
|
||||
|
@ -381,7 +386,9 @@ export class ExecutionRepository extends Repository<ExecutionEntity> {
|
|||
},
|
||||
) {
|
||||
if (!deleteConditions?.deleteBefore && !deleteConditions?.ids) {
|
||||
throw new Error('Either "deleteBefore" or "ids" must be present in the request body');
|
||||
throw new ApplicationError(
|
||||
'Either "deleteBefore" or "ids" must be present in the request body',
|
||||
);
|
||||
}
|
||||
|
||||
const query = this.createQueryBuilder('execution')
|
||||
|
|
|
@ -3,7 +3,7 @@ import { readFileSync, rmSync } from 'fs';
|
|||
import { InstanceSettings } from 'n8n-core';
|
||||
import type { ObjectLiteral } from 'typeorm';
|
||||
import type { QueryRunner } from 'typeorm/query-runner/QueryRunner';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse } from 'n8n-workflow';
|
||||
import config from '@/config';
|
||||
import { inTest } from '@/constants';
|
||||
import type { BaseMigration, Migration, MigrationContext, MigrationFn } from '@db/types';
|
||||
|
@ -23,7 +23,7 @@ function loadSurveyFromDisk(): string | null {
|
|||
const personalizationSurvey = JSON.parse(surveyFile) as object;
|
||||
const kvPairs = Object.entries(personalizationSurvey);
|
||||
if (!kvPairs.length) {
|
||||
throw new Error('personalizationSurvey is empty');
|
||||
throw new ApplicationError('personalizationSurvey is empty');
|
||||
} else {
|
||||
const emptyKeys = kvPairs.reduce((acc, [, value]) => {
|
||||
if (!value || (Array.isArray(value) && !value.length)) {
|
||||
|
@ -32,7 +32,7 @@ function loadSurveyFromDisk(): string | null {
|
|||
return acc;
|
||||
}, 0);
|
||||
if (emptyKeys === kvPairs.length) {
|
||||
throw new Error('incomplete personalizationSurvey');
|
||||
throw new ApplicationError('incomplete personalizationSurvey');
|
||||
}
|
||||
}
|
||||
return surveyFile;
|
||||
|
@ -68,7 +68,8 @@ const runDisablingForeignKeys = async (
|
|||
fn: MigrationFn,
|
||||
) => {
|
||||
const { dbType, queryRunner } = context;
|
||||
if (dbType !== 'sqlite') throw new Error('Disabling transactions only available in sqlite');
|
||||
if (dbType !== 'sqlite')
|
||||
throw new ApplicationError('Disabling transactions only available in sqlite');
|
||||
await queryRunner.query('PRAGMA foreign_keys=OFF');
|
||||
await queryRunner.startTransaction();
|
||||
try {
|
||||
|
|
|
@ -24,6 +24,7 @@ import type { BooleanLicenseFeature } from '@/Interfaces';
|
|||
import Container from 'typedi';
|
||||
import { License } from '@/License';
|
||||
import type { Scope } from '@n8n/permissions';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export const createAuthMiddleware =
|
||||
(authRole: AuthRole): RequestHandler =>
|
||||
|
@ -87,7 +88,9 @@ export const registerController = (app: Application, config: Config, cObj: objec
|
|||
| string
|
||||
| undefined;
|
||||
if (!controllerBasePath)
|
||||
throw new Error(`${controllerClass.name} is missing the RestController decorator`);
|
||||
throw new ApplicationError('Controller is missing the RestController decorator', {
|
||||
extra: { controllerName: controllerClass.name },
|
||||
});
|
||||
|
||||
const authRoles = Reflect.getMetadata(CONTROLLER_AUTH_ROLES, controllerClass) as
|
||||
| AuthRoleMetadata
|
||||
|
|
|
@ -35,6 +35,7 @@ import { InternalHooks } from '@/InternalHooks';
|
|||
import { TagRepository } from '@db/repositories/tag.repository';
|
||||
import { Logger } from '@/Logger';
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
@Service()
|
||||
export class SourceControlService {
|
||||
|
@ -83,7 +84,7 @@ export class SourceControlService {
|
|||
false,
|
||||
);
|
||||
if (!foldersExisted) {
|
||||
throw new Error();
|
||||
throw new ApplicationError('No folders exist');
|
||||
}
|
||||
if (!this.gitService.git) {
|
||||
await this.initGitService();
|
||||
|
@ -94,7 +95,7 @@ export class SourceControlService {
|
|||
branches.current !==
|
||||
this.sourceControlPreferencesService.sourceControlPreferences.branchName
|
||||
) {
|
||||
throw new Error();
|
||||
throw new ApplicationError('Branch is not set up correctly');
|
||||
}
|
||||
} catch (error) {
|
||||
throw new BadRequestError(
|
||||
|
@ -195,7 +196,7 @@ export class SourceControlService {
|
|||
await this.gitService.pull();
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to reset workfolder: ${(error as Error).message}`);
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'Unable to fetch updates from git - your folder might be out of sync. Try reconnecting from the Source Control settings page.',
|
||||
);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import { sourceControlFoldersExistCheck } from './sourceControlHelper.ee';
|
|||
import type { User } from '@db/entities/User';
|
||||
import { getInstanceOwner } from '../../UserManagement/UserManagementHelper';
|
||||
import { Logger } from '@/Logger';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
@Service()
|
||||
export class SourceControlGitService {
|
||||
|
@ -43,7 +44,7 @@ export class SourceControlGitService {
|
|||
});
|
||||
this.logger.debug(`Git binary found: ${gitResult.toString()}`);
|
||||
} catch (error) {
|
||||
throw new Error(`Git binary not found: ${(error as Error).message}`);
|
||||
throw new ApplicationError('Git binary not found', { cause: error });
|
||||
}
|
||||
try {
|
||||
const sshResult = execSync('ssh -V', {
|
||||
|
@ -51,7 +52,7 @@ export class SourceControlGitService {
|
|||
});
|
||||
this.logger.debug(`SSH binary found: ${sshResult.toString()}`);
|
||||
} catch (error) {
|
||||
throw new Error(`SSH binary not found: ${(error as Error).message}`);
|
||||
throw new ApplicationError('SSH binary not found', { cause: error });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -114,7 +115,7 @@ export class SourceControlGitService {
|
|||
|
||||
private async checkRepositorySetup(): Promise<boolean> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (async)');
|
||||
throw new ApplicationError('Git is not initialized (async)');
|
||||
}
|
||||
if (!(await this.git.checkIsRepo())) {
|
||||
return false;
|
||||
|
@ -129,7 +130,7 @@ export class SourceControlGitService {
|
|||
|
||||
private async hasRemote(remote: string): Promise<boolean> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (async)');
|
||||
throw new ApplicationError('Git is not initialized (async)');
|
||||
}
|
||||
try {
|
||||
const remotes = await this.git.getRemotes(true);
|
||||
|
@ -141,7 +142,7 @@ export class SourceControlGitService {
|
|||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Git is not initialized ${(error as Error).message}`);
|
||||
throw new ApplicationError('Git is not initialized', { cause: error });
|
||||
}
|
||||
this.logger.debug(`Git remote not found: ${remote}`);
|
||||
return false;
|
||||
|
@ -155,7 +156,7 @@ export class SourceControlGitService {
|
|||
user: User,
|
||||
): Promise<void> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (Promise)');
|
||||
throw new ApplicationError('Git is not initialized (Promise)');
|
||||
}
|
||||
if (sourceControlPreferences.initRepo) {
|
||||
try {
|
||||
|
@ -193,7 +194,7 @@ export class SourceControlGitService {
|
|||
|
||||
async setGitUserDetails(name: string, email: string): Promise<void> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (setGitUserDetails)');
|
||||
throw new ApplicationError('Git is not initialized (setGitUserDetails)');
|
||||
}
|
||||
await this.git.addConfig('user.email', email);
|
||||
await this.git.addConfig('user.name', name);
|
||||
|
@ -201,7 +202,7 @@ export class SourceControlGitService {
|
|||
|
||||
async getBranches(): Promise<{ branches: string[]; currentBranch: string }> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (getBranches)');
|
||||
throw new ApplicationError('Git is not initialized (getBranches)');
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -218,13 +219,13 @@ export class SourceControlGitService {
|
|||
currentBranch: current,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Could not get remote branches from repository ${(error as Error).message}`);
|
||||
throw new ApplicationError('Could not get remote branches from repository', { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
async setBranch(branch: string): Promise<{ branches: string[]; currentBranch: string }> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (setBranch)');
|
||||
throw new ApplicationError('Git is not initialized (setBranch)');
|
||||
}
|
||||
await this.git.checkout(branch);
|
||||
await this.git.branch([`--set-upstream-to=${SOURCE_CONTROL_ORIGIN}/${branch}`, branch]);
|
||||
|
@ -233,7 +234,7 @@ export class SourceControlGitService {
|
|||
|
||||
async getCurrentBranch(): Promise<{ current: string; remote: string }> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (getCurrentBranch)');
|
||||
throw new ApplicationError('Git is not initialized (getCurrentBranch)');
|
||||
}
|
||||
const currentBranch = (await this.git.branch()).current;
|
||||
return {
|
||||
|
@ -244,7 +245,7 @@ export class SourceControlGitService {
|
|||
|
||||
async diffRemote(): Promise<DiffResult | undefined> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (diffRemote)');
|
||||
throw new ApplicationError('Git is not initialized (diffRemote)');
|
||||
}
|
||||
const currentBranch = await this.getCurrentBranch();
|
||||
if (currentBranch.remote) {
|
||||
|
@ -256,7 +257,7 @@ export class SourceControlGitService {
|
|||
|
||||
async diffLocal(): Promise<DiffResult | undefined> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (diffLocal)');
|
||||
throw new ApplicationError('Git is not initialized (diffLocal)');
|
||||
}
|
||||
const currentBranch = await this.getCurrentBranch();
|
||||
if (currentBranch.remote) {
|
||||
|
@ -268,14 +269,14 @@ export class SourceControlGitService {
|
|||
|
||||
async fetch(): Promise<FetchResult> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (fetch)');
|
||||
throw new ApplicationError('Git is not initialized (fetch)');
|
||||
}
|
||||
return this.git.fetch();
|
||||
}
|
||||
|
||||
async pull(options: { ffOnly: boolean } = { ffOnly: true }): Promise<PullResult> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (pull)');
|
||||
throw new ApplicationError('Git is not initialized (pull)');
|
||||
}
|
||||
const params = {};
|
||||
if (options.ffOnly) {
|
||||
|
@ -293,7 +294,7 @@ export class SourceControlGitService {
|
|||
): Promise<PushResult> {
|
||||
const { force, branch } = options;
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized ({)');
|
||||
throw new ApplicationError('Git is not initialized ({)');
|
||||
}
|
||||
if (force) {
|
||||
return this.git.push(SOURCE_CONTROL_ORIGIN, branch, ['-f']);
|
||||
|
@ -303,7 +304,7 @@ export class SourceControlGitService {
|
|||
|
||||
async stage(files: Set<string>, deletedFiles?: Set<string>): Promise<string> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (stage)');
|
||||
throw new ApplicationError('Git is not initialized (stage)');
|
||||
}
|
||||
if (deletedFiles?.size) {
|
||||
try {
|
||||
|
@ -319,7 +320,7 @@ export class SourceControlGitService {
|
|||
options: { hard: boolean; target: string } = { hard: true, target: 'HEAD' },
|
||||
): Promise<string> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (Promise)');
|
||||
throw new ApplicationError('Git is not initialized (Promise)');
|
||||
}
|
||||
if (options?.hard) {
|
||||
return this.git.raw(['reset', '--hard', options.target]);
|
||||
|
@ -331,14 +332,14 @@ export class SourceControlGitService {
|
|||
|
||||
async commit(message: string): Promise<CommitResult> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (commit)');
|
||||
throw new ApplicationError('Git is not initialized (commit)');
|
||||
}
|
||||
return this.git.commit(message);
|
||||
}
|
||||
|
||||
async status(): Promise<StatusResult> {
|
||||
if (!this.git) {
|
||||
throw new Error('Git is not initialized (status)');
|
||||
throw new ApplicationError('Git is not initialized (status)');
|
||||
}
|
||||
const statusResult = await this.git.status();
|
||||
return statusResult;
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
SOURCE_CONTROL_WORKFLOW_EXPORT_FOLDER,
|
||||
} from './constants';
|
||||
import glob from 'fast-glob';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse } from 'n8n-workflow';
|
||||
import { readFile as fsReadFile } from 'fs/promises';
|
||||
import { Credentials, InstanceSettings } from 'n8n-core';
|
||||
import type { IWorkflowToImport } from '@/Interfaces';
|
||||
|
@ -63,7 +63,7 @@ export class SourceControlImportService {
|
|||
const globalOwnerRole = await Container.get(RoleService).findGlobalOwnerRole();
|
||||
|
||||
if (!globalOwnerRole) {
|
||||
throw new Error(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
return globalOwnerRole;
|
||||
|
@ -73,7 +73,7 @@ export class SourceControlImportService {
|
|||
const credentialOwnerRole = await Container.get(RoleService).findCredentialOwnerRole();
|
||||
|
||||
if (!credentialOwnerRole) {
|
||||
throw new Error(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
return credentialOwnerRole;
|
||||
|
@ -83,7 +83,7 @@ export class SourceControlImportService {
|
|||
const workflowOwnerRole = await Container.get(RoleService).findWorkflowOwnerRole();
|
||||
|
||||
if (!workflowOwnerRole) {
|
||||
throw new Error(`Failed to find owner workflow role. ${UM_FIX_INSTRUCTION}`);
|
||||
throw new ApplicationError(`Failed to find owner workflow role. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
return workflowOwnerRole;
|
||||
|
@ -255,7 +255,9 @@ export class SourceControlImportService {
|
|||
['id'],
|
||||
);
|
||||
if (upsertResult?.identifiers?.length !== 1) {
|
||||
throw new Error(`Failed to upsert workflow ${importedWorkflow.id ?? 'new'}`);
|
||||
throw new ApplicationError('Failed to upsert workflow', {
|
||||
extra: { workflowId: importedWorkflow.id ?? 'new' },
|
||||
});
|
||||
}
|
||||
// Update workflow owner to the user who exported the workflow, if that user exists
|
||||
// in the instance, and the workflow doesn't already have an owner
|
||||
|
@ -435,7 +437,7 @@ export class SourceControlImportService {
|
|||
select: ['id'],
|
||||
});
|
||||
if (findByName && findByName.id !== tag.id) {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
`A tag with the name <strong>${tag.name}</strong> already exists locally.<br />Please either rename the local tag, or the remote one with the id <strong>${tag.id}</strong> in the tags.json file.`,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
sourceControlFoldersExistCheck,
|
||||
} from './sourceControlHelper.ee';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse } from 'n8n-workflow';
|
||||
import {
|
||||
SOURCE_CONTROL_SSH_FOLDER,
|
||||
SOURCE_CONTROL_GIT_FOLDER,
|
||||
|
@ -150,7 +150,9 @@ export class SourceControlPreferencesService {
|
|||
validationError: { target: false },
|
||||
});
|
||||
if (validationResult.length > 0) {
|
||||
throw new Error(`Invalid source control preferences: ${JSON.stringify(validationResult)}`);
|
||||
throw new ApplicationError('Invalid source control preferences', {
|
||||
extra: { preferences: validationResult },
|
||||
});
|
||||
}
|
||||
return validationResult;
|
||||
}
|
||||
|
@ -177,7 +179,7 @@ export class SourceControlPreferencesService {
|
|||
loadOnStartup: true,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to save source control preferences: ${(error as Error).message}`);
|
||||
throw new ApplicationError('Failed to save source control preferences', { cause: error });
|
||||
}
|
||||
}
|
||||
return this.sourceControlPreferences;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { validate as jsonSchemaValidate } from 'jsonschema';
|
||||
import type { IWorkflowBase, JsonObject, ExecutionStatus } from 'n8n-workflow';
|
||||
import { jsonParse, Workflow, WorkflowOperationError } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse, Workflow, WorkflowOperationError } from 'n8n-workflow';
|
||||
import type { FindOperator } from 'typeorm';
|
||||
import { In } from 'typeorm';
|
||||
import { ActiveExecutions } from '@/ActiveExecutions';
|
||||
|
@ -234,7 +234,7 @@ export class ExecutionsService {
|
|||
}
|
||||
|
||||
if (execution.finished) {
|
||||
throw new Error('The execution succeeded, so it cannot be retried.');
|
||||
throw new ApplicationError('The execution succeeded, so it cannot be retried.');
|
||||
}
|
||||
|
||||
const executionMode = 'retry';
|
||||
|
@ -276,8 +276,9 @@ export class ExecutionsService {
|
|||
})) as IWorkflowBase;
|
||||
|
||||
if (workflowData === undefined) {
|
||||
throw new Error(
|
||||
`The workflow with the ID "${workflowId}" could not be found and so the data not be loaded for the retry.`,
|
||||
throw new ApplicationError(
|
||||
'Workflow could not be found and so the data not be loaded for the retry.',
|
||||
{ extra: { workflowId } },
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -324,7 +325,7 @@ export class ExecutionsService {
|
|||
await Container.get(ActiveExecutions).getPostExecutePromise(retriedExecutionId);
|
||||
|
||||
if (!executionData) {
|
||||
throw new Error('The retry did not start for an unknown reason.');
|
||||
throw new ApplicationError('The retry did not start for an unknown reason.');
|
||||
}
|
||||
|
||||
return !!executionData.finished;
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
import { isObjectLiteral } from '@/utils';
|
||||
import { plainToInstance, instanceToPlain } from 'class-transformer';
|
||||
import { validate } from 'class-validator';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse } from 'n8n-workflow';
|
||||
|
||||
export class BaseFilter {
|
||||
protected static async toFilter(rawFilter: string, Filter: typeof BaseFilter) {
|
||||
const dto = jsonParse(rawFilter, { errorMessage: 'Failed to parse filter JSON' });
|
||||
|
||||
if (!isObjectLiteral(dto)) throw new Error('Filter must be an object literal');
|
||||
if (!isObjectLiteral(dto)) throw new ApplicationError('Filter must be an object literal');
|
||||
|
||||
const instance = plainToInstance(Filter, dto, {
|
||||
excludeExtraneousValues: true, // remove fields not in class
|
||||
|
@ -25,6 +25,6 @@ export class BaseFilter {
|
|||
private async validate() {
|
||||
const result = await validate(this);
|
||||
|
||||
if (result.length > 0) throw new Error('Parsed filter does not fit the schema');
|
||||
if (result.length > 0) throw new ApplicationError('Parsed filter does not fit the schema');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import { isStringArray } from '@/utils';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse } from 'n8n-workflow';
|
||||
|
||||
export class BaseSelect {
|
||||
static selectableFields: Set<string>;
|
||||
|
@ -9,7 +9,7 @@ export class BaseSelect {
|
|||
protected static toSelect(rawFilter: string, Select: typeof BaseSelect) {
|
||||
const dto = jsonParse(rawFilter, { errorMessage: 'Failed to parse filter JSON' });
|
||||
|
||||
if (!isStringArray(dto)) throw new Error('Parsed select is not a string array');
|
||||
if (!isStringArray(dto)) throw new ApplicationError('Parsed select is not a string array');
|
||||
|
||||
return dto.reduce<Record<string, true>>((acc, field) => {
|
||||
if (!Select.selectableFields.has(field)) return acc;
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { isIntegerString } from '@/utils';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export class Pagination {
|
||||
static fromString(rawTake: string, rawSkip: string) {
|
||||
if (!isIntegerString(rawTake)) {
|
||||
throw new Error('Parameter take is not an integer string');
|
||||
throw new ApplicationError('Parameter take is not an integer string');
|
||||
}
|
||||
|
||||
if (!isIntegerString(rawSkip)) {
|
||||
throw new Error('Parameter skip is not an integer string');
|
||||
throw new ApplicationError('Parameter skip is not an integer string');
|
||||
}
|
||||
|
||||
const [take, skip] = [rawTake, rawSkip].map((o) => parseInt(o, 10));
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as ResponseHelper from '@/ResponseHelper';
|
|||
import { Pagination } from './dtos/pagination.dto';
|
||||
import type { ListQuery } from '@/requests';
|
||||
import type { RequestHandler } from 'express';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export const paginationListQueryMiddleware: RequestHandler = (
|
||||
req: ListQuery.Request,
|
||||
|
@ -13,7 +14,7 @@ export const paginationListQueryMiddleware: RequestHandler = (
|
|||
|
||||
try {
|
||||
if (!rawTake && req.query.skip) {
|
||||
throw new Error('Please specify `take` when using `skip`');
|
||||
throw new ApplicationError('Please specify `take` when using `skip`');
|
||||
}
|
||||
|
||||
if (!rawTake) return next();
|
||||
|
|
|
@ -3,7 +3,7 @@ import config from '@/config';
|
|||
import { caching } from 'cache-manager';
|
||||
import type { MemoryCache } from 'cache-manager';
|
||||
import type { RedisCache } from 'cache-manager-ioredis-yet';
|
||||
import { jsonStringify } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonStringify } from 'n8n-workflow';
|
||||
import { getDefaultRedisClient, getRedisPrefix } from './redis/RedisServiceHelper';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
|
@ -161,7 +161,9 @@ export class CacheService extends EventEmitter {
|
|||
this.emit(this.metricsCounterEvents.cacheUpdate);
|
||||
const refreshValues: unknown[] = await options.refreshFunctionMany(keys);
|
||||
if (keys.length !== refreshValues.length) {
|
||||
throw new Error('refreshFunctionMany must return the same number of values as keys');
|
||||
throw new ApplicationError(
|
||||
'refreshFunctionMany must return the same number of values as keys',
|
||||
);
|
||||
}
|
||||
const newKV: Array<[string, unknown]> = [];
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
|
@ -191,7 +193,7 @@ export class CacheService extends EventEmitter {
|
|||
}
|
||||
if (this.isRedisCache()) {
|
||||
if (!(this.cache as RedisCache)?.store?.isCacheable(value)) {
|
||||
throw new Error('Value is not cacheable');
|
||||
throw new ApplicationError('Value is not cacheable');
|
||||
}
|
||||
}
|
||||
await this.cache?.store.set(key, value, ttl);
|
||||
|
@ -215,7 +217,7 @@ export class CacheService extends EventEmitter {
|
|||
if (this.isRedisCache()) {
|
||||
nonNullValues.forEach(([_key, value]) => {
|
||||
if (!(this.cache as RedisCache)?.store?.isCacheable(value)) {
|
||||
throw new Error('Value is not cacheable');
|
||||
throw new ApplicationError('Value is not cacheable');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -301,7 +303,7 @@ export class CacheService extends EventEmitter {
|
|||
}
|
||||
return map;
|
||||
}
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'Keys and values do not match, this should not happen and appears to result from some cache corruption.',
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Service } from 'typedi';
|
|||
import { promisify } from 'util';
|
||||
import axios from 'axios';
|
||||
|
||||
import type { PublicInstalledPackage } from 'n8n-workflow';
|
||||
import { ApplicationError, type PublicInstalledPackage } from 'n8n-workflow';
|
||||
import { InstanceSettings } from 'n8n-core';
|
||||
import type { PackageDirectoryLoader } from 'n8n-core';
|
||||
|
||||
|
@ -93,10 +93,10 @@ export class CommunityPackagesService {
|
|||
}
|
||||
|
||||
parseNpmPackageName(rawString?: string): CommunityPackages.ParsedPackageName {
|
||||
if (!rawString) throw new Error(PACKAGE_NAME_NOT_PROVIDED);
|
||||
if (!rawString) throw new ApplicationError(PACKAGE_NAME_NOT_PROVIDED);
|
||||
|
||||
if (INVALID_OR_SUSPICIOUS_PACKAGE_NAME.test(rawString)) {
|
||||
throw new Error('Package name must be a single word');
|
||||
throw new ApplicationError('Package name must be a single word');
|
||||
}
|
||||
|
||||
const scope = rawString.includes('/') ? rawString.split('/')[0] : undefined;
|
||||
|
@ -104,7 +104,7 @@ export class CommunityPackagesService {
|
|||
const packageNameWithoutScope = scope ? rawString.replace(`${scope}/`, '') : rawString;
|
||||
|
||||
if (!packageNameWithoutScope.startsWith(NODE_PACKAGE_PREFIX)) {
|
||||
throw new Error(`Package name must start with ${NODE_PACKAGE_PREFIX}`);
|
||||
throw new ApplicationError(`Package name must start with ${NODE_PACKAGE_PREFIX}`);
|
||||
}
|
||||
|
||||
const version = packageNameWithoutScope.includes('@')
|
||||
|
@ -155,12 +155,12 @@ export class CommunityPackagesService {
|
|||
};
|
||||
|
||||
Object.entries(map).forEach(([npmMessage, n8nMessage]) => {
|
||||
if (errorMessage.includes(npmMessage)) throw new Error(n8nMessage);
|
||||
if (errorMessage.includes(npmMessage)) throw new ApplicationError(n8nMessage);
|
||||
});
|
||||
|
||||
this.logger.warn('npm command failed', { errorMessage });
|
||||
|
||||
throw new Error(PACKAGE_FAILED_TO_INSTALL);
|
||||
throw new ApplicationError(PACKAGE_FAILED_TO_INSTALL);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -327,7 +327,7 @@ export class CommunityPackagesService {
|
|||
await this.executeNpmCommand(command);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND) {
|
||||
throw new Error(`The npm package "${packageName}" could not be found.`);
|
||||
throw new ApplicationError('npm package not found', { extra: { packageName } });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
@ -341,7 +341,7 @@ export class CommunityPackagesService {
|
|||
try {
|
||||
await this.executeNpmCommand(removeCommand);
|
||||
} catch {}
|
||||
throw new Error(RESPONSE_ERROR_MESSAGES.PACKAGE_LOADING_FAILED, { cause: error });
|
||||
throw new ApplicationError(RESPONSE_ERROR_MESSAGES.PACKAGE_LOADING_FAILED, { cause: error });
|
||||
}
|
||||
|
||||
if (loader.loadedNodes.length > 0) {
|
||||
|
@ -354,7 +354,10 @@ export class CommunityPackagesService {
|
|||
await this.loadNodesAndCredentials.postProcessLoaders();
|
||||
return installedPackage;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to save installed package: ${packageName}`, { cause: error });
|
||||
throw new ApplicationError('Failed to save installed package', {
|
||||
extra: { packageName },
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Remove this package since it contains no loadable nodes
|
||||
|
@ -363,7 +366,7 @@ export class CommunityPackagesService {
|
|||
await this.executeNpmCommand(removeCommand);
|
||||
} catch {}
|
||||
|
||||
throw new Error(RESPONSE_ERROR_MESSAGES.PACKAGE_DOES_NOT_CONTAIN_NODES);
|
||||
throw new ApplicationError(RESPONSE_ERROR_MESSAGES.PACKAGE_DOES_NOT_CONTAIN_NODES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import type {
|
|||
INodeParameters,
|
||||
INodeTypeNameVersion,
|
||||
} from 'n8n-workflow';
|
||||
import { Workflow, RoutingNode } from 'n8n-workflow';
|
||||
import { Workflow, RoutingNode, ApplicationError } from 'n8n-workflow';
|
||||
import { NodeExecuteFunctions } from 'n8n-core';
|
||||
import { NodeTypes } from '@/NodeTypes';
|
||||
|
||||
|
@ -57,8 +57,9 @@ export class DynamicNodeParametersService {
|
|||
// requiring a baseURL to be defined can at least not a random server be called.
|
||||
// In the future this code has to get improved that it does not use the request information from
|
||||
// the request rather resolves it via the parameter-path and nodeType data.
|
||||
throw new Error(
|
||||
`The node-type "${nodeType.description.name}" does not exist or does not have "requestDefaults.baseURL" defined!`,
|
||||
throw new ApplicationError(
|
||||
'Node type does not exist or does not have "requestDefaults.baseURL" defined!',
|
||||
{ tags: { nodeType: nodeType.description.name } },
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -114,7 +115,7 @@ export class DynamicNodeParametersService {
|
|||
}
|
||||
|
||||
if (!Array.isArray(optionsData)) {
|
||||
throw new Error('The returned data is not an array!');
|
||||
throw new ApplicationError('The returned data is not an array');
|
||||
}
|
||||
|
||||
return optionsData[0].map((item) => item.json) as unknown as INodePropertyOptions[];
|
||||
|
@ -182,9 +183,10 @@ export class DynamicNodeParametersService {
|
|||
) {
|
||||
const method = nodeType.methods?.[type]?.[methodName];
|
||||
if (typeof method !== 'function') {
|
||||
throw new Error(
|
||||
`The node-type "${nodeType.description.name}" does not have the method "${methodName}" defined!`,
|
||||
);
|
||||
throw new ApplicationError('Node type does not have method defined', {
|
||||
tags: { nodeType: nodeType.description.name },
|
||||
extra: { methodName },
|
||||
});
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { UserService } from './user.service';
|
|||
import type { Credentials, ListQuery } from '@/requests';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
@Service()
|
||||
export class OwnershipService {
|
||||
|
@ -27,7 +28,7 @@ export class OwnershipService {
|
|||
|
||||
const workflowOwnerRole = await this.roleService.findWorkflowOwnerRole();
|
||||
|
||||
if (!workflowOwnerRole) throw new Error('Failed to find workflow owner role');
|
||||
if (!workflowOwnerRole) throw new ApplicationError('Failed to find workflow owner role');
|
||||
|
||||
const sharedWorkflow = await this.sharedWorkflowRepository.findOneOrFail({
|
||||
where: { workflowId, roleId: workflowOwnerRole.id },
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type express from 'express';
|
||||
import Container, { Service } from 'typedi';
|
||||
import type { User } from '@db/entities/User';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse } from 'n8n-workflow';
|
||||
import { getServiceProviderInstance } from './serviceProvider.ee';
|
||||
import type { SamlUserAttributes } from './types/samlUserAttributes';
|
||||
import { isSsoJustInTimeProvisioningEnabled } from '../ssoHelpers';
|
||||
|
@ -93,7 +93,7 @@ export class SamlService {
|
|||
validate: async (response: string) => {
|
||||
const valid = await validateResponse(response);
|
||||
if (!valid) {
|
||||
throw new Error('Invalid SAML response');
|
||||
throw new ApplicationError('Invalid SAML response');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -101,7 +101,7 @@ export class SamlService {
|
|||
|
||||
getIdentityProviderInstance(forceRecreate = false): IdentityProviderInstance {
|
||||
if (this.samlify === undefined) {
|
||||
throw new Error('Samlify is not initialized');
|
||||
throw new ApplicationError('Samlify is not initialized');
|
||||
}
|
||||
if (this.identityProviderInstance === undefined || forceRecreate) {
|
||||
this.identityProviderInstance = this.samlify.IdentityProvider({
|
||||
|
@ -114,7 +114,7 @@ export class SamlService {
|
|||
|
||||
getServiceProviderInstance(): ServiceProviderInstance {
|
||||
if (this.samlify === undefined) {
|
||||
throw new Error('Samlify is not initialized');
|
||||
throw new ApplicationError('Samlify is not initialized');
|
||||
}
|
||||
return getServiceProviderInstance(this._samlPreferences, this.samlify);
|
||||
}
|
||||
|
@ -225,7 +225,7 @@ export class SamlService {
|
|||
} else if (prefs.metadata) {
|
||||
const validationResult = await validateMetadata(prefs.metadata);
|
||||
if (!validationResult) {
|
||||
throw new Error('Invalid SAML metadata');
|
||||
throw new ApplicationError('Invalid SAML metadata');
|
||||
}
|
||||
}
|
||||
this.getIdentityProviderInstance(true);
|
||||
|
|
|
@ -11,7 +11,7 @@ import type {
|
|||
WorkflowWithSharingsAndCredentials,
|
||||
} from './workflows.types';
|
||||
import { CredentialsService } from '@/credentials/credentials.service';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
import { ApplicationError, NodeOperationError } from 'n8n-workflow';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import Container from 'typedi';
|
||||
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||
|
@ -161,7 +161,9 @@ export class EEWorkflowsService extends WorkflowsService {
|
|||
if (credentialId === undefined) return;
|
||||
const matchedCredential = allowedCredentials.find(({ id }) => id === credentialId);
|
||||
if (!matchedCredential) {
|
||||
throw new Error('The workflow contains credentials that you do not have access to');
|
||||
throw new ApplicationError(
|
||||
'The workflow contains credentials that you do not have access to',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -51,10 +51,7 @@ describe('WorkflowCredentials', () => {
|
|||
});
|
||||
|
||||
test('Should return an error if credentials cannot be found in the DB', async () => {
|
||||
const credentials = notFoundNode.credentials!.test;
|
||||
const expectedError = new Error(
|
||||
`Could not find credentials for type "test" with ID "${credentials.id}".`,
|
||||
);
|
||||
const expectedError = new Error('Could not find credential.');
|
||||
await expect(WorkflowCredentials([notFoundNode])).rejects.toEqual(expectedError);
|
||||
expect(credentialsRepository.findOneBy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue