Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(core): Migrate all errors in cli package to new hierarchy #13478

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
15 changes: 9 additions & 6 deletions packages/cli/src/__tests__/external-hooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { GlobalConfig } from '@n8n/config';
import { mock } from 'jest-mock-extended';
import type { ErrorReporter, Logger } from 'n8n-core';
import type { IWorkflowBase } from 'n8n-workflow';
import { ApplicationError } from 'n8n-workflow';
import { UnexpectedError } from 'n8n-workflow';

import type { CredentialsRepository } from '@/databases/repositories/credentials.repository';
import type { SettingsRepository } from '@/databases/repositories/settings.repository';
Expand Down Expand Up @@ -57,7 +57,7 @@ describe('ExternalHooks', () => {
{ virtual: true },
);

await expect(externalHooks.init()).rejects.toThrow(ApplicationError);
await expect(externalHooks.init()).rejects.toThrow(UnexpectedError);
});

it('should successfully load hooks from valid hook file', async () => {
Expand Down Expand Up @@ -112,10 +112,13 @@ describe('ExternalHooks', () => {
externalHooks['registered']['workflow.create'] = [hookFn];

await expect(externalHooks.run('workflow.create', [workflowData])).rejects.toThrow(error);

expect(errorReporter.error).toHaveBeenCalledWith(expect.any(ApplicationError), {
level: 'fatal',
});
expect(errorReporter.error).toHaveBeenCalledWith(
expect.objectContaining({
message: 'External hook "workflow.create" failed',
cause: error,
}),
{ level: 'fatal' },
);
expect(logger.error).toHaveBeenCalledWith(
'There was a problem running hook "workflow.create"',
);
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/active-workflow-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
Workflow,
WorkflowActivationError,
WebhookPathTakenError,
ApplicationError,
UnexpectedError,
} from 'n8n-workflow';

import { ActivationErrorsService } from '@/activation-errors.service';
Expand Down Expand Up @@ -238,7 +238,7 @@ export class ActiveWorkflowManager {
});

if (workflowData === null) {
throw new ApplicationError('Could not find workflow', { extra: { workflowId } });
throw new UnexpectedError('Could not find workflow', { extra: { workflowId } });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could change the .findOne above to .findOneOrFail and remove this if, as non BaseErrors are treated as unexpected

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to change any logic in this PR if possible.

}

const workflow = new Workflow({
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/collaboration/collaboration.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { PushPayload } from '@n8n/api-types';
import { Service } from '@n8n/di';
import { ErrorReporter } from 'n8n-core';
import type { Workflow } from 'n8n-workflow';
import { ApplicationError } from 'n8n-workflow';
import { UnexpectedError } from 'n8n-workflow';

import { CollaborationState } from '@/collaboration/collaboration.state';
import type { User } from '@/databases/entities/user';
Expand Down Expand Up @@ -34,7 +34,7 @@ export class CollaborationService {
await this.handleUserMessage(event.userId, event.msg);
} catch (error) {
this.errorReporter.error(
new ApplicationError('Error handling CollaborationService push message', {
new UnexpectedError('Error handling CollaborationService push message', {
extra: {
msg: event.msg,
userId: event.userId,
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/audit.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SecurityConfig } from '@n8n/config';
import { Container } from '@n8n/di';
import { Flags } from '@oclif/core';
import { ApplicationError } from 'n8n-workflow';
import { UserError } from 'n8n-workflow';

import { RISK_CATEGORIES } from '@/security-audit/constants';
import { SecurityAuditService } from '@/security-audit/security-audit.service';
Expand Down Expand Up @@ -48,7 +48,7 @@ export class SecurityAudit extends BaseCommand {

const hint = `Valid categories are: ${RISK_CATEGORIES.join(', ')}`;

throw new ApplicationError([message, hint].join('. '));
throw new UserError([message, hint].join('. '));
}

const result = await Container.get(SecurityAuditService).run(
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
DataDeduplicationService,
ErrorReporter,
} from 'n8n-core';
import { ApplicationError, ensureError, sleep } from 'n8n-workflow';
import { ensureError, sleep, UserError } from 'n8n-workflow';

import type { AbstractServer } from '@/abstract-server';
import config from '@/config';
Expand Down Expand Up @@ -151,7 +151,7 @@ export abstract class BaseCommand extends Command {
if (!isSelected && !isAvailable) return;

if (isSelected && !isAvailable) {
throw new ApplicationError(
throw new UserError(
'External storage selected but unavailable. Please make external storage available by adding "s3" to `N8N_AVAILABLE_BINARY_DATA_MODES`.',
);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/execute-batch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import fs from 'fs';
import { diff } from 'json-diff';
import pick from 'lodash/pick';
import type { IRun, ITaskData, IWorkflowBase, IWorkflowExecutionDataProcess } from 'n8n-workflow';
import { ApplicationError, jsonParse } from 'n8n-workflow';
import { jsonParse, UnexpectedError } from 'n8n-workflow';
import os from 'os';
import { sep } from 'path';

Expand Down Expand Up @@ -472,7 +472,7 @@ export class ExecuteBatch extends BaseCommand {
this.updateStatus();
}
} else {
throw new ApplicationError('Wrong execution status - cannot proceed');
throw new UnexpectedError('Wrong execution status - cannot proceed');
}
});
}
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/src/commands/execute.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Container } from '@n8n/di';
import { Flags } from '@oclif/core';
import type { IWorkflowBase, IWorkflowExecutionDataProcess } from 'n8n-workflow';
import { ApplicationError, ExecutionBaseError } from 'n8n-workflow';
import { ExecutionBaseError, UnexpectedError, UserError } from 'n8n-workflow';

import { ActiveExecutions } from '@/active-executions';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
Expand Down Expand Up @@ -44,7 +44,7 @@ export class Execute extends BaseCommand {
}

if (flags.file) {
throw new ApplicationError(
throw new UserError(
'The --file flag is no longer supported. Please first import the workflow and then execute it using the --id flag.',
{ level: 'warning' },
);
Expand All @@ -64,7 +64,7 @@ export class Execute extends BaseCommand {
}

if (!workflowData) {
throw new ApplicationError('Failed to retrieve workflow data for requested workflow');
throw new UnexpectedError('Failed to retrieve workflow data for requested workflow');
}

if (!isWorkflowIdValid(workflowId)) {
Expand All @@ -87,7 +87,7 @@ export class Execute extends BaseCommand {
const data = await activeExecutions.getPostExecutePromise(executionId);

if (data === undefined) {
throw new ApplicationError('Workflow did not return any data');
throw new UnexpectedError('Workflow did not return any data');
}

if (data.data.resultData.error) {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/export/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Container } from '@n8n/di';
import { Flags } from '@oclif/core';
import fs from 'fs';
import { Credentials } from 'n8n-core';
import { ApplicationError } from 'n8n-workflow';
import { UserError } from 'n8n-workflow';
import path from 'path';

import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
Expand Down Expand Up @@ -123,7 +123,7 @@ export class ExportCredentialsCommand extends BaseCommand {
}

if (credentials.length === 0) {
throw new ApplicationError('No credentials found with specified filters');
throw new UserError('No credentials found with specified filters');
}

if (flags.separate) {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/export/workflow.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Container } from '@n8n/di';
import { Flags } from '@oclif/core';
import fs from 'fs';
import { ApplicationError } from 'n8n-workflow';
import { UserError } from 'n8n-workflow';
import path from 'path';

import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
Expand Down Expand Up @@ -107,7 +107,7 @@ export class ExportWorkflowsCommand extends BaseCommand {
});

if (workflows.length === 0) {
throw new ApplicationError('No workflows found with specified filters');
throw new UserError('No workflows found with specified filters');
}

if (flags.separate) {
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/src/commands/import/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import glob from 'fast-glob';
import fs from 'fs';
import { Cipher } from 'n8n-core';
import type { ICredentialsEncrypted } from 'n8n-workflow';
import { ApplicationError, jsonParse } from 'n8n-workflow';
import { jsonParse, UserError } from 'n8n-workflow';

import { UM_FIX_INSTRUCTION } from '@/constants';
import { CredentialsEntity } from '@/databases/entities/credentials-entity';
Expand Down Expand Up @@ -66,7 +66,7 @@ export class ImportCredentialsCommand extends BaseCommand {
}

if (flags.projectId && flags.userId) {
throw new ApplicationError(
throw new UserError(
'You cannot use `--userId` and `--projectId` together. Use one or the other.',
);
}
Expand All @@ -81,7 +81,7 @@ export class ImportCredentialsCommand extends BaseCommand {
const result = await this.checkRelations(credentials, flags.projectId, flags.userId);

if (!result.success) {
throw new ApplicationError(result.message);
throw new UserError(result.message);
}

for (const credential of credentials) {
Expand Down Expand Up @@ -202,7 +202,7 @@ export class ImportCredentialsCommand extends BaseCommand {
);

if (!Array.isArray(credentialsUnchecked)) {
throw new ApplicationError(
throw new UserError(
'File does not seem to contain credentials. Make sure the credentials are contained in an array.',
);
}
Expand Down Expand Up @@ -252,7 +252,7 @@ export class ImportCredentialsCommand extends BaseCommand {
if (!userId) {
const owner = await this.transactionManager.findOneBy(User, { role: 'global:owner' });
if (!owner) {
throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
throw new UserError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
}
userId = owner.id;
}
Expand Down
12 changes: 6 additions & 6 deletions packages/cli/src/commands/import/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Flags } from '@oclif/core';
import glob from 'fast-glob';
import fs from 'fs';
import type { IWorkflowBase, WorkflowId } from 'n8n-workflow';
import { ApplicationError, jsonParse } from 'n8n-workflow';
import { jsonParse, UserError } from 'n8n-workflow';

import { UM_FIX_INSTRUCTION } from '@/constants';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
Expand All @@ -19,7 +19,7 @@ import { BaseCommand } from '../base-command';

function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IWorkflowToImport[] {
if (!Array.isArray(workflows)) {
throw new ApplicationError(
throw new UserError(
'File does not seem to contain workflows. Make sure the workflows are contained in an array.',
);
}
Expand All @@ -30,7 +30,7 @@ function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IW
!Object.prototype.hasOwnProperty.call(workflow, 'nodes') ||
!Object.prototype.hasOwnProperty.call(workflow, 'connections')
) {
throw new ApplicationError('File does not seem to contain valid workflows.');
throw new UserError('File does not seem to contain valid workflows.');
}
}
}
Expand Down Expand Up @@ -81,7 +81,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
}

if (flags.projectId && flags.userId) {
throw new ApplicationError(
throw new UserError(
'You cannot use `--userId` and `--projectId` together. Use one or the other.',
);
}
Expand All @@ -93,7 +93,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
const result = await this.checkRelations(workflows, flags.projectId, flags.userId);

if (!result.success) {
throw new ApplicationError(result.message);
throw new UserError(result.message);
}

this.logger.info(`Importing ${workflows.length} workflows...`);
Expand Down Expand Up @@ -220,7 +220,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
if (!userId) {
const owner = await Container.get(UserRepository).findOneBy({ role: 'global:owner' });
if (!owner) {
throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
throw new UserError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
}
userId = owner.id;
}
Expand Down
16 changes: 8 additions & 8 deletions packages/cli/src/commands/ldap/reset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Container } from '@n8n/di';
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
import { In } from '@n8n/typeorm';
import { Flags } from '@oclif/core';
import { ApplicationError } from 'n8n-workflow';
import { UserError } from 'n8n-workflow';

import { UM_FIX_INSTRUCTION } from '@/constants';
import { CredentialsService } from '@/credentials/credentials.service';
Expand Down Expand Up @@ -56,7 +56,7 @@ export class Reset extends BaseCommand {
Number(!!flags.deleteWorkflowsAndCredentials);

if (numberOfOptions !== 1) {
throw new ApplicationError(wrongFlagsError);
throw new UserError(wrongFlagsError);
}

const owner = await this.getOwner();
Expand All @@ -71,13 +71,13 @@ export class Reset extends BaseCommand {
// Migrate all workflows and credentials to another project.
if (flags.projectId ?? flags.userId) {
if (flags.userId && ldapIdentities.some((i) => i.userId === flags.userId)) {
throw new ApplicationError(
throw new UserError(
`Can't migrate workflows and credentials to the user with the ID ${flags.userId}. That user was created via LDAP and will be deleted as well.`,
);
}

if (flags.projectId && personalProjectIds.includes(flags.projectId)) {
throw new ApplicationError(
throw new UserError(
`Can't migrate workflows and credentials to the project with the ID ${flags.projectId}. That project is a personal project belonging to a user that was created via LDAP and will be deleted as well.`,
);
}
Expand Down Expand Up @@ -132,7 +132,7 @@ export class Reset extends BaseCommand {
const project = await Container.get(ProjectRepository).findOneBy({ id: projectId });

if (project === null) {
throw new ApplicationError(`Could not find the project with the ID ${projectId}.`);
throw new UserError(`Could not find the project with the ID ${projectId}.`);
}

return project;
Expand All @@ -142,15 +142,15 @@ export class Reset extends BaseCommand {
const project = await Container.get(ProjectRepository).getPersonalProjectForUser(userId);

if (project === null) {
throw new ApplicationError(
throw new UserError(
`Could not find the user with the ID ${userId} or their personalProject.`,
);
}

return project;
}

throw new ApplicationError(wrongFlagsError);
throw new UserError(wrongFlagsError);
}

async catch(error: Error): Promise<void> {
Expand All @@ -161,7 +161,7 @@ export class Reset extends BaseCommand {
private async getOwner() {
const owner = await Container.get(UserRepository).findOneBy({ role: 'global:owner' });
if (!owner) {
throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
throw new UserError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
}

return owner;
Expand Down
6 changes: 2 additions & 4 deletions packages/cli/src/commands/webhook.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Container } from '@n8n/di';
import { Flags } from '@oclif/core';
import { ApplicationError } from 'n8n-workflow';
import { UserError } from 'n8n-workflow';

import { ActiveExecutions } from '@/active-executions';
import config from '@/config';
Expand Down Expand Up @@ -84,9 +84,7 @@ export class Webhook extends BaseCommand {

async run() {
if (this.globalConfig.multiMainSetup.enabled) {
throw new ApplicationError(
'Webhook process cannot be started when multi-main setup is enabled.',
);
throw new UserError('Webhook process cannot be started when multi-main setup is enabled.');
}

const { ScalingService } = await import('@/scaling/scaling.service');
Expand Down
Loading
Loading