Improve node error handling (#1309)

* Add path mapping and response error interfaces

* Add error handling and throwing functionality

* Refactor error handling into a single function

* Re-implement error handling in Hacker News node

* Fix linting details

* Re-implement error handling in Spotify node

* Re-implement error handling in G Suite Admin node

* 🚧 create basic setup NodeError

* 🚧 add httpCodes

* 🚧 add path priolist

* 🚧 handle statusCode in error, adjust interfaces

* 🚧 fixing type issues w/Ivan

* 🚧 add error exploration

* 👔 fix linter issues

* 🔧 improve object check

* 🚧 remove path passing from NodeApiError

* 🚧 add multi error + refactor findProperty method

* 👔 allow any

* 🔧 handle multi error message callback

*  change return type of callback

*  add customCallback to MultiError

* 🚧 refactor to use INode

* 🔨 handle arrays, continue search after first null property found

* 🚫 refactor method access

* 🚧 setup NodeErrorView

*  change timestamp to Date.now

* 📚 Add documentation for methods and constants

* 🚧 change message setting

* 🚚 move NodeErrors to workflow

*  add new ErrorView for Nodes

* 🎨 improve error notification

* 🎨 refactor interfaces

*  add WorkflowOperationError, refactor error throwing

* 👕 fix linter issues

* 🎨 rename param

* 🐛 fix handling normal errors

*  add usage of NodeApiError

* 🎨 fix throw new error instead of constructor

* 🎨 remove unnecessary code/comments

* 🎨 adjusted spacing + updated status messages

* 🎨 fix tab indentation

*  Replace current errors with custom errors (#1576)

*  Introduce NodeApiError in catch blocks

*  Introduce NodeOperationError in nodes

*  Add missing errors and remove incompatible

*  Fix NodeOperationError in incompatible nodes

* 🔧 Adjust error handling in missed nodes

PayPal, FileMaker, Reddit, Taiga and Facebook Graph API nodes

* 🔨 Adjust Strava Trigger node error handling

* 🔨 Adjust AWS nodes error handling

* 🔨 Remove duplicate instantiation of NodeApiError

* 🐛 fix strava trigger node error handling

* Add XML parsing to NodeApiError constructor (#1633)

* 🐛 Remove type annotation from catch variable

*  Add XML parsing to NodeApiError

*  Simplify error handling in Rekognition node

*  Pass in XML flag in generic functions

* 🔥 Remove try/catch wrappers at call sites

* 🔨 Refactor setting description from XML

* 🔨 Refactor let to const in resource loaders

*  Find property in parsed XML

*  Change let to const

* 🔥 Remove unneeded try/catch block

* 👕 Fix linting issues

* 🐛 Fix errors from merge conflict resolution

*  Add custom errors to latest contributions

* 👕 Fix linting issues

*  Refactor MongoDB helpers for custom errors

* 🐛 Correct custom error type

*  Apply feedback to A nodes

*  Apply feedback to missed A node

*  Apply feedback to B-D nodes

*  Apply feedback to E-F nodes

*  Apply feedback to G nodes

*  Apply feedback to H-L nodes

*  Apply feedback to M nodes

*  Apply feedback to P nodes

*  Apply feedback to R nodes

*  Apply feedback to S nodes

*  Apply feedback to T nodes

*  Apply feedback to V-Z nodes

*  Add HTTP code to iterable node error

* 🔨 Standardize e as error

* 🔨 Standardize err as error

*  Fix error handling for non-standard nodes

Co-authored-by: Ben Hesseldieck <b.hesseldieck@gmail.com>

Co-authored-by: Ben Hesseldieck <b.hesseldieck@gmail.com>
Co-authored-by: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com>
This commit is contained in:
Iván Ovejero 2021-04-16 18:33:36 +02:00 committed by GitHub
parent 1a0e129921
commit 1d27a9e87e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
374 changed files with 2041 additions and 2826 deletions

View file

@ -167,10 +167,11 @@ export class Execute extends Command {
this.log('===================================='); this.log('====================================');
this.log(JSON.stringify(data, null, 2)); this.log(JSON.stringify(data, null, 2));
// console.log(data.data.resultData.error); const { error } = data.data.resultData;
const error = new Error(data.data.resultData.error.message); throw {
error.stack = data.data.resultData.error.stack; ...error,
throw error; stack: error.stack,
};
} }
this.log('Execution was successfull:'); this.log('Execution was successfull:');
@ -182,7 +183,6 @@ export class Execute extends Command {
console.error(e.message); console.error(e.message);
console.error(e.stack); console.error(e.stack);
this.exit(1); this.exit(1);
return;
} }
this.exit(); this.exit();

View file

@ -1,10 +1,10 @@
import { import {
ExecutionError,
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject,
ICredentialsDecrypted, ICredentialsDecrypted,
ICredentialsEncrypted, ICredentialsEncrypted,
ICredentialType, ICredentialType,
IDataObject, IDataObject,
IExecutionError,
IRun, IRun,
IRunData, IRunData,
IRunExecutionData, IRunExecutionData,
@ -18,7 +18,6 @@ import {
IDeferredPromise, IDeferredPromise,
} from 'n8n-core'; } from 'n8n-core';
import * as PCancelable from 'p-cancelable'; import * as PCancelable from 'p-cancelable';
import { ObjectID, Repository } from 'typeorm'; import { ObjectID, Repository } from 'typeorm';
@ -374,10 +373,10 @@ export interface ITransferNodeTypes {
export interface IWorkflowErrorData { export interface IWorkflowErrorData {
[key: string]: IDataObject | string | number | IExecutionError; [key: string]: IDataObject | string | number | ExecutionError;
execution: { execution: {
id?: string; id?: string;
error: IExecutionError; error: ExecutionError;
lastNodeExecuted: string; lastNodeExecuted: string;
mode: WorkflowExecuteMode; mode: WorkflowExecuteMode;
}; };

View file

@ -144,7 +144,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
try { try {
webhookResultData = await workflow.runWebhook(webhookData, workflowStartNode, additionalData, NodeExecuteFunctions, executionMode); webhookResultData = await workflow.runWebhook(webhookData, workflowStartNode, additionalData, NodeExecuteFunctions, executionMode);
} catch (e) { } catch (err) {
// Send error response to webhook caller // Send error response to webhook caller
const errorMessage = 'Workflow Webhook Error: Workflow could not be started!'; const errorMessage = 'Workflow Webhook Error: Workflow could not be started!';
responseCallback(new Error(errorMessage), {}); responseCallback(new Error(errorMessage), {});
@ -156,8 +156,9 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
runData: {}, runData: {},
lastNodeExecuted: workflowStartNode.name, lastNodeExecuted: workflowStartNode.name,
error: { error: {
message: e.message, ...err,
stack: e.stack, message: err.message,
stack: err.stack,
}, },
}, },
}; };

View file

@ -627,9 +627,11 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
} else { } else {
await ActiveExecutions.getInstance().remove(executionId, data); await ActiveExecutions.getInstance().remove(executionId, data);
// Workflow did fail // Workflow did fail
const error = new Error(data.data.resultData.error!.message); const { error } = data.data.resultData;
error.stack = data.data.resultData.error!.stack; throw {
throw error; ...error,
stack: error!.stack,
};
} }
} }

View file

@ -27,12 +27,12 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, ExecutionError,
IExecutionError,
IRun, IRun,
Workflow, Workflow,
WorkflowExecuteMode, WorkflowExecuteMode,
WorkflowHooks, WorkflowHooks,
WorkflowOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import * as config from '../config'; import * as config from '../config';
@ -78,13 +78,13 @@ export class WorkflowRunner {
/** /**
* The process did error * The process did error
* *
* @param {IExecutionError} error * @param {ExecutionError} error
* @param {Date} startedAt * @param {Date} startedAt
* @param {WorkflowExecuteMode} executionMode * @param {WorkflowExecuteMode} executionMode
* @param {string} executionId * @param {string} executionId
* @memberof WorkflowRunner * @memberof WorkflowRunner
*/ */
processError(error: IExecutionError, startedAt: Date, executionMode: WorkflowExecuteMode, executionId: string) { processError(error: ExecutionError, startedAt: Date, executionMode: WorkflowExecuteMode, executionId: string) {
const fullRunData: IRun = { const fullRunData: IRun = {
data: { data: {
resultData: { resultData: {
@ -250,9 +250,7 @@ export class WorkflowRunner {
const fullRunData :IRun = { const fullRunData :IRun = {
data: { data: {
resultData: { resultData: {
error: { error: new WorkflowOperationError('Workflow has been canceled!'),
message: 'Workflow has been canceled!',
} as IExecutionError,
runData: {}, runData: {},
}, },
}, },
@ -464,14 +462,14 @@ export class WorkflowRunner {
} else if (message.type === 'processError') { } else if (message.type === 'processError') {
clearTimeout(executionTimeout); clearTimeout(executionTimeout);
const executionError = message.data.executionError as IExecutionError; const executionError = message.data.executionError as ExecutionError;
this.processError(executionError, startedAt, data.executionMode, executionId); this.processError(executionError, startedAt, data.executionMode, executionId);
} else if (message.type === 'processHook') { } else if (message.type === 'processHook') {
this.processHookMessage(workflowHooks, message.data as IProcessMessageDataHook); this.processHookMessage(workflowHooks, message.data as IProcessMessageDataHook);
} else if (message.type === 'timeout') { } else if (message.type === 'timeout') {
// Execution timed out and its process has been terminated // Execution timed out and its process has been terminated
const timeoutError = { message: 'Workflow execution timed out!' } as IExecutionError; const timeoutError = new WorkflowOperationError('Workflow execution timed out!');
this.processError(timeoutError, startedAt, data.executionMode, executionId); this.processError(timeoutError, startedAt, data.executionMode, executionId);
} else if (message.type === 'startExecution') { } else if (message.type === 'startExecution') {
@ -486,16 +484,12 @@ export class WorkflowRunner {
subprocess.on('exit', (code, signal) => { subprocess.on('exit', (code, signal) => {
if (signal === 'SIGTERM'){ if (signal === 'SIGTERM'){
// Execution timed out and its process has been terminated // Execution timed out and its process has been terminated
const timeoutError = { const timeoutError = new WorkflowOperationError('Workflow execution timed out!');
message: 'Workflow execution timed out!',
} as IExecutionError;
this.processError(timeoutError, startedAt, data.executionMode, executionId); this.processError(timeoutError, startedAt, data.executionMode, executionId);
} else if (code !== 0) { } else if (code !== 0) {
// Process did exit with error code, so something went wrong. // Process did exit with error code, so something went wrong.
const executionError = { const executionError = new WorkflowOperationError('Workflow execution process did crash for an unknown reason!');
message: 'Workflow execution process did crash for an unknown reason!',
} as IExecutionError;
this.processError(executionError, startedAt, data.executionMode, executionId); this.processError(executionError, startedAt, data.executionMode, executionId);
} }

View file

@ -16,10 +16,9 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
ExecutionError,
IDataObject, IDataObject,
IExecuteData,
IExecuteWorkflowInfo, IExecuteWorkflowInfo,
IExecutionError,
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeData, INodeTypeData,
@ -30,6 +29,7 @@ import {
IWorkflowExecuteHooks, IWorkflowExecuteHooks,
Workflow, Workflow,
WorkflowHooks, WorkflowHooks,
WorkflowOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import * as config from '../config'; import * as config from '../config';
@ -270,7 +270,7 @@ process.on('message', async (message: IProcessMessage) => {
// Workflow started already executing // Workflow started already executing
runData = workflowRunner.workflowExecute.getFullRunData(workflowRunner.startedAt); runData = workflowRunner.workflowExecute.getFullRunData(workflowRunner.startedAt);
const timeOutError = message.type === 'timeout' ? { message: 'Workflow execution timed out!' } as IExecutionError : undefined; const timeOutError = message.type === 'timeout' ? new WorkflowOperationError('Workflow execution timed out!') : undefined;
// If there is any data send it to parent process, if execution timedout add the error // If there is any data send it to parent process, if execution timedout add the error
await workflowRunner.workflowExecute.processSuccessExecution(workflowRunner.startedAt, workflowRunner.workflow!, timeOutError); await workflowRunner.workflowExecute.processSuccessExecution(workflowRunner.startedAt, workflowRunner.workflow!, timeOutError);
@ -301,11 +301,14 @@ process.on('message', async (message: IProcessMessage) => {
workflowRunner.executionIdCallback(message.data.executionId); workflowRunner.executionIdCallback(message.data.executionId);
} }
} catch (error) { } catch (error) {
// Catch all uncaught errors and forward them to parent process // Catch all uncaught errors and forward them to parent process
const executionError = { const executionError = {
message: error.message, ...error,
stack: error.stack, name: error!.name || 'Error',
} as IExecutionError; message: error!.message,
stack: error!.stack,
} as ExecutionError;
await sendToParentProcess('processError', { await sendToParentProcess('processError', {
executionError, executionError,

View file

@ -18,7 +18,6 @@ import {
IWorkflowSettings as IWorkflowSettingsWorkflow, IWorkflowSettings as IWorkflowSettingsWorkflow,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { OptionsWithUri, OptionsWithUrl } from 'request'; import { OptionsWithUri, OptionsWithUrl } from 'request';
import * as requestPromise from 'request-promise-native'; import * as requestPromise from 'request-promise-native';
@ -26,7 +25,6 @@ interface Constructable<T> {
new(): T; new(): T;
} }
export interface IProcessMessage { export interface IProcessMessage {
data?: any; // tslint:disable-line:no-any data?: any; // tslint:disable-line:no-any
type: string; type: string;

View file

@ -32,6 +32,7 @@ import {
IWorkflowExecuteAdditionalData, IWorkflowExecuteAdditionalData,
IWorkflowMetadata, IWorkflowMetadata,
NodeHelpers, NodeHelpers,
NodeOperationError,
NodeParameterValue, NodeParameterValue,
Workflow, Workflow,
WorkflowActivateMode, WorkflowActivateMode,
@ -309,16 +310,16 @@ export function getCredentials(workflow: Workflow, node: INode, type: string, ad
// Get the NodeType as it has the information if the credentials are required // Get the NodeType as it has the information if the credentials are required
const nodeType = workflow.nodeTypes.getByName(node.type); const nodeType = workflow.nodeTypes.getByName(node.type);
if (nodeType === undefined) { if (nodeType === undefined) {
throw new Error(`Node type "${node.type}" is not known so can not get credentials!`); throw new NodeOperationError(node, `Node type "${node.type}" is not known so can not get credentials!`);
} }
if (nodeType.description.credentials === undefined) { if (nodeType.description.credentials === undefined) {
throw new Error(`Node type "${node.type}" does not have any credentials defined!`); throw new NodeOperationError(node, `Node type "${node.type}" does not have any credentials defined!`);
} }
const nodeCredentialDescription = nodeType.description.credentials.find((credentialTypeDescription) => credentialTypeDescription.name === type); const nodeCredentialDescription = nodeType.description.credentials.find((credentialTypeDescription) => credentialTypeDescription.name === type);
if (nodeCredentialDescription === undefined) { if (nodeCredentialDescription === undefined) {
throw new Error(`Node type "${node.type}" does not have any credentials of type "${type}" defined!`); throw new NodeOperationError(node, `Node type "${node.type}" does not have any credentials of type "${type}" defined!`);
} }
if (NodeHelpers.displayParameter(additionalData.currentNodeParameters || node.parameters, nodeCredentialDescription, node.parameters) === false) { if (NodeHelpers.displayParameter(additionalData.currentNodeParameters || node.parameters, nodeCredentialDescription, node.parameters) === false) {
@ -333,10 +334,10 @@ export function getCredentials(workflow: Workflow, node: INode, type: string, ad
if (nodeCredentialDescription.required === true) { if (nodeCredentialDescription.required === true) {
// Credentials are required so error // Credentials are required so error
if (!node.credentials) { if (!node.credentials) {
throw new Error('Node does not have any credentials set!'); throw new NodeOperationError(node,'Node does not have any credentials set!');
} }
if (!node.credentials[type]) { if (!node.credentials[type]) {
throw new Error(`Node does not have any credentials set for "${type}"!`); throw new NodeOperationError(node,`Node does not have any credentials set for "${type}"!`);
} }
} else { } else {
// Credentials are not required so resolve with undefined // Credentials are not required so resolve with undefined

View file

@ -1,10 +1,10 @@
import * as PCancelable from 'p-cancelable'; import * as PCancelable from 'p-cancelable';
import { import {
ExecutionError,
IConnection, IConnection,
IDataObject, IDataObject,
IExecuteData, IExecuteData,
IExecutionError,
INode, INode,
INodeConnections, INodeConnections,
INodeExecutionData, INodeExecutionData,
@ -17,6 +17,7 @@ import {
IWorkflowExecuteAdditionalData, IWorkflowExecuteAdditionalData,
Workflow, Workflow,
WorkflowExecuteMode, WorkflowExecuteMode,
WorkflowOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
NodeExecuteFunctions, NodeExecuteFunctions,
@ -490,7 +491,7 @@ export class WorkflowExecute {
// Variables which hold temporary data for each node-execution // Variables which hold temporary data for each node-execution
let executionData: IExecuteData; let executionData: IExecuteData;
let executionError: IExecutionError | undefined; let executionError: ExecutionError | undefined;
let executionNode: INode; let executionNode: INode;
let nodeSuccessData: INodeExecutionData[][] | null | undefined; let nodeSuccessData: INodeExecutionData[][] | null | undefined;
let runIndex: number; let runIndex: number;
@ -517,8 +518,10 @@ export class WorkflowExecute {
try { try {
await this.executeHook('workflowExecuteBefore', [workflow]); await this.executeHook('workflowExecuteBefore', [workflow]);
} catch (error) { } catch (error) {
// Set the error that it can be saved correctly // Set the error that it can be saved correctly
executionError = { executionError = {
...error,
message: error.message, message: error.message,
stack: error.stack, stack: error.stack,
}; };
@ -683,9 +686,11 @@ export class WorkflowExecute {
break; break;
} catch (error) { } catch (error) {
this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name; this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
executionError = { executionError = {
...error,
message: error.message, message: error.message,
stack: error.stack, stack: error.stack,
}; };
@ -784,7 +789,7 @@ export class WorkflowExecute {
})() })()
.then(async () => { .then(async () => {
if (gotCancel && executionError === undefined) { if (gotCancel && executionError === undefined) {
return this.processSuccessExecution(startedAt, workflow, { message: 'Workflow has been canceled!' } as IExecutionError); return this.processSuccessExecution(startedAt, workflow, new WorkflowOperationError('Workflow has been canceled!'));
} }
return this.processSuccessExecution(startedAt, workflow, executionError); return this.processSuccessExecution(startedAt, workflow, executionError);
}) })
@ -792,6 +797,7 @@ export class WorkflowExecute {
const fullRunData = this.getFullRunData(startedAt); const fullRunData = this.getFullRunData(startedAt);
fullRunData.data.resultData.error = { fullRunData.data.resultData.error = {
...error,
message: error.message, message: error.message,
stack: error.stack, stack: error.stack,
}; };
@ -815,7 +821,7 @@ export class WorkflowExecute {
// @ts-ignore // @ts-ignore
async processSuccessExecution(startedAt: Date, workflow: Workflow, executionError?: IExecutionError): PCancelable<IRun> { async processSuccessExecution(startedAt: Date, workflow: Workflow, executionError?: ExecutionError): PCancelable<IRun> {
const fullRunData = this.getFullRunData(startedAt); const fullRunData = this.getFullRunData(startedAt);
if (executionError !== undefined) { if (executionError !== undefined) {

View file

@ -0,0 +1,177 @@
<template>
<div>
<div class="error-header">
<div class="error-message">ERROR: {{error.message}}</div>
<div class="error-description" v-if="error.description">{{error.description}}</div>
</div>
<details>
<summary class="error-details__summary">
<font-awesome-icon class="error-details__icon" icon="angle-right" /> Details
</summary>
<div class="error-details__content">
<div v-if="error.timestamp">
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix box-card__title">
<span>Time</span>
</div>
<div>
{{new Date(error.timestamp).toLocaleString()}}
</div>
</el-card>
</div>
<div v-if="error.httpCode">
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix box-card__title">
<span>HTTP-Code</span>
</div>
<div>
{{error.httpCode}}
</div>
</el-card>
</div>
<div v-if="error.cause">
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix box-card__title">
<span>Cause</span>
<br>
<span class="box-card__subtitle">Data below may contain sensitive information. Proceed with caution when sharing.</span>
</div>
<div>
<el-button class="copy-button" @click="copyCause" circle type="text" title="Copy to clipboard">
<font-awesome-icon icon="copy" />
</el-button>
<vue-json-pretty
:data="error.cause"
:deep="3"
:showLength="true"
selectableType="single"
path="error"
class="json-data"
/>
</div>
</el-card>
</div>
<div v-if="error.stack">
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix box-card__title">
<span>Stack</span>
</div>
<div>
<pre><code>{{error.stack}}</code></pre>
</div>
</el-card>
</div>
</div>
</details>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
//@ts-ignore
import VueJsonPretty from 'vue-json-pretty';
import { copyPaste } from '@/components/mixins/copyPaste';
import { showMessage } from '@/components/mixins/showMessage';
import mixins from 'vue-typed-mixins';
export default mixins(
copyPaste,
showMessage,
).extend({
name: 'NodeErrorView',
props: [
'error',
],
components: {
VueJsonPretty,
},
methods: {
copyCause() {
this.copyToClipboard(JSON.stringify(this.error.cause));
this.copySuccess();
},
copySuccess() {
this.$showMessage({
title: 'Copied to clipboard',
message: '',
type: 'info',
});
},
},
});
</script>
<style lang="scss">
.error-header {
margin-bottom: 10px;
}
.error-message {
color: #ff0000;
font-weight: bold;
font-size: 1.1rem;
}
.error-description {
margin-top: 10px;
font-size: 1rem;
}
.error-details__summary {
font-weight: 600;
font-size: 16px;
cursor: pointer;
outline:none;
}
.error-details__icon {
margin-right: 4px;
}
details > summary {
list-style-type: none;
}
details > summary::-webkit-details-marker {
display: none;
}
details[open] {
.error-details__icon {
transform: rotate(90deg);
}
}
.error-details__content {
margin-top: 15px;
}
.el-divider__text {
background-color: #f9f9f9;
}
.box-card {
margin-top: 1em;
overflow: auto;
}
.box-card__title {
font-weight: 400;
}
.box-card__subtitle {
font-weight: 200;
font-style: italic;
font-size: 0.7rem;
}
.copy-button {
position: absolute;
font-size: 1.1rem;
right: 50px;
z-index: 1000;
}
</style>

View file

@ -81,8 +81,7 @@
<div class="data-display-content"> <div class="data-display-content">
<span v-if="node && workflowRunData !== null && workflowRunData.hasOwnProperty(node.name)"> <span v-if="node && workflowRunData !== null && workflowRunData.hasOwnProperty(node.name)">
<div v-if="workflowRunData[node.name][runIndex].error" class="error-display"> <div v-if="workflowRunData[node.name][runIndex].error" class="error-display">
<div class="error-message">ERROR: {{workflowRunData[node.name][runIndex].error.message}}</div> <NodeErrorView :error="workflowRunData[node.name][runIndex].error" />
<pre><code>{{workflowRunData[node.name][runIndex].error.stack}}</code></pre>
</div> </div>
<span v-else> <span v-else>
<div v-if="showData === false" class="to-much-data"> <div v-if="showData === false" class="to-much-data">
@ -226,6 +225,7 @@ import {
} from '@/constants'; } from '@/constants';
import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue'; import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue';
import NodeErrorView from '@/components/Error/NodeViewError.vue';
import { copyPaste } from '@/components/mixins/copyPaste'; import { copyPaste } from '@/components/mixins/copyPaste';
import { genericHelpers } from '@/components/mixins/genericHelpers'; import { genericHelpers } from '@/components/mixins/genericHelpers';
@ -247,6 +247,7 @@ export default mixins(
name: 'RunData', name: 'RunData',
components: { components: {
BinaryDataDisplay, BinaryDataDisplay,
NodeErrorView,
VueJsonPretty, VueJsonPretty,
}, },
data () { data () {
@ -739,13 +740,6 @@ export default mixins(
} }
} }
.error-display {
.error-message {
color: #ff0000;
font-weight: bold;
}
}
table { table {
border-collapse: collapse; border-collapse: collapse;
text-align: left; text-align: left;

View file

@ -207,8 +207,19 @@ export const pushConnection = mixins(
if (runDataExecuted.finished !== true) { if (runDataExecuted.finished !== true) {
// There was a problem with executing the workflow // There was a problem with executing the workflow
let errorMessage = 'There was a problem executing the workflow!'; let errorMessage = 'There was a problem executing the workflow!';
if (runDataExecuted.data.resultData.error && runDataExecuted.data.resultData.error.message) { if (runDataExecuted.data.resultData.error && runDataExecuted.data.resultData.error.message) {
errorMessage = `There was a problem executing the workflow:<br /><strong>"${runDataExecuted.data.resultData.error.message}"</strong>`; let nodeName: string | undefined;
if (runDataExecuted.data.resultData.error.node) {
nodeName = typeof runDataExecuted.data.resultData.error.node === 'string'
? runDataExecuted.data.resultData.error.node
: runDataExecuted.data.resultData.error.node.name;
}
const receivedError = nodeName
? `${nodeName}: ${runDataExecuted.data.resultData.error.message}`
: runDataExecuted.data.resultData.error.message;
errorMessage = `There was a problem executing the workflow:<br /><strong>"${receivedError}"</strong>`;
} }
this.$titleSet(workflow.name, 'ERROR'); this.$titleSet(workflow.name, 'ERROR');
this.$showMessage({ this.$showMessage({

View file

@ -459,6 +459,10 @@ h1, h2, h3, h4, h5, h6 {
border: none; border: none;
} }
.el-notification__content {
text-align: left;
}
// Custom scrollbar // Custom scrollbar
::-webkit-scrollbar { ::-webkit-scrollbar {

View file

@ -9,6 +9,7 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -431,7 +432,7 @@ export class ActiveCampaign implements INodeType {
addAdditionalFields(body.contact as IDataObject, updateFields); addAdditionalFields(body.contact as IDataObject, updateFields);
} else { } else {
throw new Error(`The operation "${operation}" is not known`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
} }
} else if (resource === 'account') { } else if (resource === 'account') {
if (operation === 'create') { if (operation === 'create') {
@ -512,7 +513,7 @@ export class ActiveCampaign implements INodeType {
addAdditionalFields(body.account as IDataObject, updateFields); addAdditionalFields(body.account as IDataObject, updateFields);
} else { } else {
throw new Error(`The operation "${operation}" is not known`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
} }
} else if (resource === 'accountContact') { } else if (resource === 'accountContact') {
if (operation === 'create') { if (operation === 'create') {
@ -562,7 +563,7 @@ export class ActiveCampaign implements INodeType {
endpoint = `/api/3/accountContacts/${accountContactId}`; endpoint = `/api/3/accountContacts/${accountContactId}`;
} else { } else {
throw new Error(`The operation "${operation}" is not known`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
} }
} else if (resource === 'contactTag') { } else if (resource === 'contactTag') {
if (operation === 'add') { if (operation === 'add') {
@ -592,7 +593,7 @@ export class ActiveCampaign implements INodeType {
endpoint = `/api/3/contactTags/${contactTagId}`; endpoint = `/api/3/contactTags/${contactTagId}`;
} else { } else {
throw new Error(`The operation "${operation}" is not known`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
} }
} else if (resource === 'contactList') { } else if (resource === 'contactList') {
if (operation === 'add') { if (operation === 'add') {
@ -630,7 +631,7 @@ export class ActiveCampaign implements INodeType {
dataKey = 'contacts'; dataKey = 'contacts';
} else { } else {
throw new Error(`The operation "${operation}" is not known`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
} }
} else if (resource === 'list') { } else if (resource === 'list') {
if (operation === 'getAll') { if (operation === 'getAll') {
@ -732,7 +733,7 @@ export class ActiveCampaign implements INodeType {
addAdditionalFields(body.tag as IDataObject, updateFields); addAdditionalFields(body.tag as IDataObject, updateFields);
} else { } else {
throw new Error(`The operation "${operation}" is not known`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
} }
} else if (resource === 'deal') { } else if (resource === 'deal') {
if (operation === 'create') { if (operation === 'create') {
@ -851,7 +852,7 @@ export class ActiveCampaign implements INodeType {
endpoint = `/api/3/deals/${dealId}/notes/${dealNoteId}`; endpoint = `/api/3/deals/${dealId}/notes/${dealNoteId}`;
} else { } else {
throw new Error(`The operation "${operation}" is not known`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
} }
} else if (resource === 'connection') { } else if (resource === 'connection') {
if (operation === 'create') { if (operation === 'create') {
@ -926,7 +927,7 @@ export class ActiveCampaign implements INodeType {
endpoint = `/api/3/connections`; endpoint = `/api/3/connections`;
} else { } else {
throw new Error(`The operation "${operation}" is not known`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
} }
} else if (resource === 'ecommerceOrder') { } else if (resource === 'ecommerceOrder') {
if (operation === 'create') { if (operation === 'create') {
@ -1024,7 +1025,7 @@ export class ActiveCampaign implements INodeType {
endpoint = `/api/3/ecomOrders`; endpoint = `/api/3/ecomOrders`;
} else { } else {
throw new Error(`The operation "${operation}" is not known`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
} }
} else if (resource === 'ecommerceCustomer') { } else if (resource === 'ecommerceCustomer') {
if (operation === 'create') { if (operation === 'create') {
@ -1114,7 +1115,7 @@ export class ActiveCampaign implements INodeType {
endpoint = `/api/3/ecomCustomers`; endpoint = `/api/3/ecomCustomers`;
} else { } else {
throw new Error(`The operation "${operation}" is not known`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
} }
} else if (resource === 'ecommerceOrderProducts') { } else if (resource === 'ecommerceOrderProducts') {
if (operation === 'getByProductId') { if (operation === 'getByProductId') {
@ -1160,11 +1161,11 @@ export class ActiveCampaign implements INodeType {
endpoint = `/api/3/ecomOrderProducts`; endpoint = `/api/3/ecomOrderProducts`;
} else { } else {
throw new Error(`The operation "${operation}" is not known`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
} }
} else { } else {
throw new Error(`The resource "${resource}" is not known!`); throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`);
} }
let responseData; let responseData;

View file

@ -116,7 +116,7 @@ export class ActiveCampaignTrigger implements INodeType {
const endpoint = `/api/3/webhooks/${webhookData.webhookId}`; const endpoint = `/api/3/webhooks/${webhookData.webhookId}`;
try { try {
await activeCampaignApiRequest.call(this, 'GET', endpoint, {}); await activeCampaignApiRequest.call(this, 'GET', endpoint, {});
} catch (e) { } catch (error) {
return false; return false;
} }
return true; return true;

View file

@ -4,7 +4,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, ILoadOptionsFunctions, INodeProperties, IDataObject, ILoadOptionsFunctions, INodeProperties, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { OptionsWithUri } from 'request'; import { OptionsWithUri } from 'request';
@ -28,7 +28,7 @@ export interface IProduct {
export async function activeCampaignApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: IDataObject, query?: IDataObject, dataKey?: string): Promise<any> { // tslint:disable-line:no-any export async function activeCampaignApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: IDataObject, query?: IDataObject, dataKey?: string): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('activeCampaignApi'); const credentials = this.getCredentials('activeCampaignApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
if (query === undefined) { if (query === undefined) {
@ -53,7 +53,7 @@ export async function activeCampaignApiRequest(this: IHookFunctions | IExecuteFu
const responseData = await this.helpers.request!(options); const responseData = await this.helpers.request!(options);
if (responseData.success === false) { if (responseData.success === false) {
throw new Error(`ActiveCampaign error response: ${responseData.error} (${responseData.error_info})`); throw new NodeApiError(this.getNode(), responseData);
} }
if (dataKey === undefined) { if (dataKey === undefined) {
@ -63,13 +63,7 @@ export async function activeCampaignApiRequest(this: IHookFunctions | IExecuteFu
} }
} catch (error) { } catch (error) {
if (error.statusCode === 403) { throw new NodeApiError(this.getNode(), error);
// Return a clear error
throw new Error('The ActiveCampaign credentials are not valid!');
}
// If that data does not exist for some reason return the actual error
throw error;
} }
} }

View file

@ -6,7 +6,7 @@ import {
ILoadOptionsFunctions, ILoadOptionsFunctions,
IWebhookFunctions, IWebhookFunctions,
} from 'n8n-core'; } from 'n8n-core';
import { IDataObject } from 'n8n-workflow'; import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow';
export async function acuitySchedulingApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function acuitySchedulingApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const authenticationMethod = this.getNodeParameter('authentication', 0); const authenticationMethod = this.getNodeParameter('authentication', 0);
@ -27,7 +27,7 @@ export async function acuitySchedulingApiRequest(this: IHookFunctions | IExecute
if (authenticationMethod === 'apiKey') { if (authenticationMethod === 'apiKey') {
const credentials = this.getCredentials('acuitySchedulingApi'); const credentials = this.getCredentials('acuitySchedulingApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
options.auth = { options.auth = {
@ -42,6 +42,6 @@ export async function acuitySchedulingApiRequest(this: IHookFunctions | IExecute
return await this.helpers.requestOAuth2!.call(this, 'acuitySchedulingOAuth2Api', options, true); return await this.helpers.requestOAuth2!.call(this, 'acuitySchedulingOAuth2Api', options, true);
} }
} catch (error) { } catch (error) {
throw new Error('Acuity Scheduling Error: ' + error.message); throw new NodeApiError(this.getNode(), error);
} }
} }

View file

@ -8,6 +8,7 @@ import {
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
IWebhookResponseData, IWebhookResponseData,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -187,7 +188,7 @@ export class AffinityTrigger implements INodeType {
const webhookUrl = this.getNodeWebhookUrl('default') as string; const webhookUrl = this.getNodeWebhookUrl('default') as string;
if (webhookUrl.includes('%20')) { if (webhookUrl.includes('%20')) {
throw new Error('The name of the Affinity Trigger Node is not allowed to contain any spaces!'); throw new NodeOperationError(this.getNode(), 'The name of the Affinity Trigger Node is not allowed to contain any spaces!');
} }
const events = this.getNodeParameter('events') as string[]; const events = this.getNodeParameter('events') as string[];

View file

@ -12,6 +12,8 @@ import {
IDataObject, IDataObject,
IHookFunctions, IHookFunctions,
IWebhookFunctions, IWebhookFunctions,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function affinityApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function affinityApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
@ -19,7 +21,7 @@ export async function affinityApiRequest(this: IExecuteFunctions | IWebhookFunct
const credentials = this.getCredentials('affinityApi'); const credentials = this.getCredentials('affinityApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const apiKey = `:${credentials.apiKey}`; const apiKey = `:${credentials.apiKey}`;
@ -47,11 +49,7 @@ export async function affinityApiRequest(this: IExecuteFunctions | IWebhookFunct
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
if (error.response) { throw new NodeApiError(this.getNode(), error);
const errorMessage = error.response.body.message || error.response.body.description || error.message;
throw new Error(`Affinity error response [${error.statusCode}]: ${errorMessage}`);
}
throw error;
} }
} }

View file

@ -3,7 +3,8 @@ import {
IDataObject, IDataObject,
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -149,7 +150,7 @@ export class AgileCrm implements INodeType {
Object.assign(body, JSON.parse(additionalFieldsJson)); Object.assign(body, JSON.parse(additionalFieldsJson));
} else { } else {
throw new Error('Additional fields must be a valid JSON'); throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON');
} }
} }
@ -305,7 +306,7 @@ export class AgileCrm implements INodeType {
Object.assign(body, JSON.parse(additionalFieldsJson)); Object.assign(body, JSON.parse(additionalFieldsJson));
} else { } else {
throw new Error('Additional fields must be a valid JSON'); throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON');
} }
} }
} else { } else {
@ -483,7 +484,7 @@ export class AgileCrm implements INodeType {
if (validateJSON(additionalFieldsJson) !== undefined) { if (validateJSON(additionalFieldsJson) !== undefined) {
Object.assign(body, JSON.parse(additionalFieldsJson)); Object.assign(body, JSON.parse(additionalFieldsJson));
} else { } else {
throw new Error('Additional fields must be a valid JSON'); throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON');
} }
} }
@ -525,7 +526,7 @@ export class AgileCrm implements INodeType {
Object.assign(body, JSON.parse(additionalFieldsJson)); Object.assign(body, JSON.parse(additionalFieldsJson));
} else { } else {
throw new Error('Additional fields must be valid JSON'); throw new NodeOperationError(this.getNode(), 'Additional fields must be valid JSON');
} }
} }

View file

@ -10,7 +10,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject, NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { IContactUpdate } from './ContactInterface'; import { IContactUpdate } from './ContactInterface';
@ -39,7 +39,7 @@ export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunction
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
throw new Error(`AgileCRM error response: ${error.message}`); throw new NodeApiError(this.getNode(), error);
} }
} }
@ -114,9 +114,9 @@ export async function agileCrmApiRequestUpdate(this: IHookFunctions | IExecuteFu
} catch (error) { } catch (error) {
if (successfulUpdates.length === 0) { if (successfulUpdates.length === 0) {
throw new Error(`AgileCRM error response: ${error.message}`); throw new NodeApiError(this.getNode(), error);
} else { } else {
throw new Error(`Not all properties updated. Updated properties: ${successfulUpdates.join(', ')} \n \nAgileCRM error response: ${error.message}`); throw new NodeApiError(this.getNode(), error, { message: `Not all properties updated. Updated properties: ${successfulUpdates.join(', ')}`, description: error.message, httpCode: error.statusCode });
} }
} }

View file

@ -7,6 +7,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -636,7 +637,7 @@ export class Airtable implements INodeType {
} }
} else { } else {
throw new Error(`The operation "${operation}" is not known!`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`);
} }
return [this.helpers.returnJsonArray(returnData)]; return [this.helpers.returnJsonArray(returnData)];

View file

@ -7,6 +7,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -170,7 +171,7 @@ export class AirtableTrigger implements INodeType {
if (Array.isArray(records) && records.length) { if (Array.isArray(records) && records.length) {
if (this.getMode() === 'manual' && records[0].fields[triggerField] === undefined) { if (this.getMode() === 'manual' && records[0].fields[triggerField] === undefined) {
throw new Error(`The Field "${triggerField}" does not exist.`); throw new NodeOperationError(this.getNode(), `The Field "${triggerField}" does not exist.`);
} }
if (downloadAttachments === true) { if (downloadAttachments === true) {

View file

@ -13,6 +13,8 @@ import {
IDataObject, IDataObject,
INodeExecutionData, INodeExecutionData,
IPollFunctions, IPollFunctions,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -41,7 +43,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa
const credentials = this.getCredentials('airtableApi'); const credentials = this.getCredentials('airtableApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
query = query || {}; query = query || {};
@ -73,23 +75,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
if (error.statusCode === 401) { throw new NodeApiError(this.getNode(), error);
// Return a clear error
throw new Error('The Airtable credentials are not valid!');
}
if (error.response && error.response.body && error.response.body.error) {
// Try to return the error prettier
const airtableError = error.response.body.error;
if (airtableError.type && airtableError.message) {
throw new Error(`Airtable error response [${airtableError.type}]: ${airtableError.message}`);
}
}
// Expected error data did not get returned so rhow the actual error
throw error;
} }
} }

View file

@ -11,6 +11,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export class Amqp implements INodeType { export class Amqp implements INodeType {
@ -98,7 +99,7 @@ export class Amqp implements INodeType {
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const credentials = this.getCredentials('amqp'); const credentials = this.getCredentials('amqp');
if (!credentials) { if (!credentials) {
throw new Error('Credentials are mandatory!'); throw new NodeOperationError(this.getNode(), 'Credentials are mandatory!');
} }
const sink = this.getNodeParameter('sink', 0, '') as string; const sink = this.getNodeParameter('sink', 0, '') as string;
@ -116,7 +117,7 @@ export class Amqp implements INodeType {
} }
if (sink === '') { if (sink === '') {
throw new Error('Queue or Topic required!'); throw new NodeOperationError(this.getNode(), 'Queue or Topic required!');
} }
const container = create_container(); const container = create_container();

View file

@ -12,6 +12,7 @@ import {
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
ITriggerResponse, ITriggerResponse,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -133,7 +134,7 @@ export class AmqpTrigger implements INodeType {
const credentials = this.getCredentials('amqp'); const credentials = this.getCredentials('amqp');
if (!credentials) { if (!credentials) {
throw new Error('Credentials are mandatory!'); throw new NodeOperationError(this.getNode(), 'Credentials are mandatory!');
} }
const sink = this.getNodeParameter('sink', '') as string; const sink = this.getNodeParameter('sink', '') as string;
@ -146,7 +147,7 @@ export class AmqpTrigger implements INodeType {
const containerReconnectLimit = options.reconnectLimit as number || 50; const containerReconnectLimit = options.reconnectLimit as number || 50;
if (sink === '') { if (sink === '') {
throw new Error('Queue or Topic required!'); throw new NodeOperationError(this.getNode(), 'Queue or Topic required!');
} }
let durable = false; let durable = false;

View file

@ -9,6 +9,7 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -466,7 +467,7 @@ export class ApiTemplateIo implements INodeType {
if (overrideJson !== '') { if (overrideJson !== '') {
const data = validateJSON(overrideJson); const data = validateJSON(overrideJson);
if (data === undefined) { if (data === undefined) {
throw new Error('A valid JSON must be provided.'); throw new NodeOperationError(this.getNode(), 'A valid JSON must be provided.');
} }
body.overrides = data; body.overrides = data;
} }
@ -523,14 +524,14 @@ export class ApiTemplateIo implements INodeType {
if (jsonParameters === false) { if (jsonParameters === false) {
const properties = (this.getNodeParameter('propertiesUi', i) as IDataObject || {}).propertyValues as IDataObject[] || []; const properties = (this.getNodeParameter('propertiesUi', i) as IDataObject || {}).propertyValues as IDataObject[] || [];
if (properties.length === 0) { if (properties.length === 0) {
throw new Error('The parameter properties cannot be empty'); throw new NodeOperationError(this.getNode(), 'The parameter properties cannot be empty');
} }
data = properties.reduce((obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), {}); data = properties.reduce((obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), {});
} else { } else {
const propertiesJson = this.getNodeParameter('propertiesJson', i) as string; const propertiesJson = this.getNodeParameter('propertiesJson', i) as string;
data = validateJSON(propertiesJson); data = validateJSON(propertiesJson);
if (data === undefined) { if (data === undefined) {
throw new Error('A valid JSON must be provided.'); throw new NodeOperationError(this.getNode(), 'A valid JSON must be provided.');
} }
} }

View file

@ -6,6 +6,7 @@ import {
IExecuteFunctions, IExecuteFunctions,
ILoadOptionsFunctions, ILoadOptionsFunctions,
} from 'n8n-core'; } from 'n8n-core';
import { NodeApiError } from 'n8n-workflow';
export async function apiTemplateIoApiRequest( export async function apiTemplateIoApiRequest(
this: IExecuteFunctions | ILoadOptionsFunctions, this: IExecuteFunctions | ILoadOptionsFunctions,
@ -42,14 +43,11 @@ export async function apiTemplateIoApiRequest(
try { try {
const response = await this.helpers.request!(options); const response = await this.helpers.request!(options);
if (response.status === 'error') { if (response.status === 'error') {
throw new Error(response.message); throw new NodeApiError(this.getNode(), response.message);
} }
return response; return response;
} catch (error) { } catch (error) {
if (error?.response?.body?.message) { throw new NodeApiError(this.getNode(), error);
throw new Error(`APITemplate.io error response [${error.statusCode}]: ${error.response.body.message}`);
}
throw error;
} }
} }

View file

@ -9,6 +9,8 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -1639,7 +1641,7 @@ export class Asana implements INodeType {
const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {}); const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {});
if (responseData.data === undefined) { if (responseData.data === undefined) {
throw new Error('No data got returned'); throw new NodeApiError(this.getNode(), responseData, { message: 'No data got returned' });
} }
const returnData: INodePropertyOptions[] = []; const returnData: INodePropertyOptions[] = [];
@ -1674,7 +1676,7 @@ export class Asana implements INodeType {
const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {}); const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {});
if (responseData.data === undefined) { if (responseData.data === undefined) {
throw new Error('No data got returned'); throw new NodeApiError(this.getNode(), responseData, { message: 'No data got returned' });
} }
const returnData: INodePropertyOptions[] = []; const returnData: INodePropertyOptions[] = [];
@ -1711,7 +1713,7 @@ export class Asana implements INodeType {
// to retrieve the teams from an organization just work with workspaces that are an organization // to retrieve the teams from an organization just work with workspaces that are an organization
if (workspace.is_organization === false) { if (workspace.is_organization === false) {
throw Error('To filter by team, the workspace selected has to be an organization'); throw new NodeOperationError(this.getNode(), 'To filter by team, the workspace selected has to be an organization');
} }
const endpoint = `/organizations/${workspaceId}/teams`; const endpoint = `/organizations/${workspaceId}/teams`;
@ -1750,15 +1752,15 @@ export class Asana implements INodeType {
let taskData; let taskData;
try { try {
taskData = await asanaApiRequest.call(this, 'GET', `/tasks/${taskId}`, {}); taskData = await asanaApiRequest.call(this, 'GET', `/tasks/${taskId}`, {});
} catch (e) { } catch (error) {
throw new Error(`Could not find task with id "${taskId}" so tags could not be loaded.`); throw new NodeApiError(this.getNode(), error, { message: `Could not find task with id "${taskId}" so tags could not be loaded.` });
} }
const workspace = taskData.data.workspace.gid; const workspace = taskData.data.workspace.gid;
const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {}, { workspace }); const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {}, { workspace });
if (responseData.data === undefined) { if (responseData.data === undefined) {
throw new Error('No data got returned'); throw new NodeApiError(this.getNode(), responseData, { message: 'No data got returned' });
} }
const returnData: INodePropertyOptions[] = []; const returnData: INodePropertyOptions[] = [];
@ -1790,7 +1792,7 @@ export class Asana implements INodeType {
const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {}); const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {});
if (responseData.data === undefined) { if (responseData.data === undefined) {
throw new Error('No data got returned'); throw new NodeApiError(this.getNode(), responseData, { message: 'No data got returned' });
} }
const returnData: INodePropertyOptions[] = []; const returnData: INodePropertyOptions[] = [];

View file

@ -10,6 +10,7 @@ import {
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
IWebhookResponseData, IWebhookResponseData,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -155,7 +156,7 @@ export class AsanaTrigger implements INodeType {
const webhookUrl = this.getNodeWebhookUrl('default') as string; const webhookUrl = this.getNodeWebhookUrl('default') as string;
if (webhookUrl.includes('%20')) { if (webhookUrl.includes('%20')) {
throw new Error('The name of the Asana Trigger Node is not allowed to contain any spaces!'); throw new NodeOperationError(this.getNode(), 'The name of the Asana Trigger Node is not allowed to contain any spaces!');
} }
const resource = this.getNodeParameter('resource') as string; const resource = this.getNodeParameter('resource') as string;
@ -189,7 +190,7 @@ export class AsanaTrigger implements INodeType {
try { try {
await asanaApiRequest.call(this, 'DELETE', endpoint, body); await asanaApiRequest.call(this, 'DELETE', endpoint, body);
} catch (e) { } catch (error) {
return false; return false;
} }

View file

@ -11,6 +11,8 @@ import {
import { import {
IDataObject, IDataObject,
INodePropertyOptions, INodePropertyOptions,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -43,7 +45,7 @@ export async function asanaApiRequest(this: IHookFunctions | IExecuteFunctions |
const credentials = this.getCredentials('asanaApi'); const credentials = this.getCredentials('asanaApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
options.headers!['Authorization'] = `Bearer ${credentials.accessToken}`; options.headers!['Authorization'] = `Bearer ${credentials.accessToken}`;
@ -54,25 +56,7 @@ export async function asanaApiRequest(this: IHookFunctions | IExecuteFunctions |
return await this.helpers.requestOAuth2.call(this, 'asanaOAuth2Api', options); return await this.helpers.requestOAuth2.call(this, 'asanaOAuth2Api', options);
} }
} catch (error) { } catch (error) {
if (error.statusCode === 401) { throw new NodeApiError(this.getNode(), error);
// Return a clear error
throw new Error('The Asana credentials are not valid!');
}
if (error.statusCode === 403) {
throw error;
}
if (error.response && error.response.body && error.response.body.errors) {
// Try to return the error prettier
const errorMessages = error.response.body.errors.map((errorData: { message: string }) => {
return errorData.message;
});
throw new Error(`Asana error response [${error.statusCode}]: ${errorMessages.join(' | ')}`);
}
// If that data does not exist for some reason return the actual error
throw error;
} }
} }

View file

@ -9,7 +9,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject, NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function automizyApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise<any> { // tslint:disable-line:no-any export async function automizyApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise<any> { // tslint:disable-line:no-any
@ -40,14 +40,7 @@ export async function automizyApiRequest(this: IExecuteFunctions | IExecuteSingl
//@ts-ignore //@ts-ignore
return await this.helpers.request.call(this, options); return await this.helpers.request.call(this, options);
} catch (error) { } catch (error) {
if (error.response && error.response.body) { throw new NodeApiError(this.getNode(), error);
throw new Error(
`Automizy error response [${error.statusCode}]: ${error.response.body.title}`,
);
}
throw error;
} }
} }

View file

@ -11,6 +11,7 @@ import {
IDataObject, IDataObject,
IHookFunctions, IHookFunctions,
IWebhookFunctions, IWebhookFunctions,
NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function autopilotApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function autopilotApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
@ -42,11 +43,7 @@ export async function autopilotApiRequest(this: IExecuteFunctions | IWebhookFunc
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
if (error.response) { throw new NodeApiError(this.getNode(), error);
const errorMessage = error.response.body.message || error.response.body.description || error.message;
throw new Error(`Autopilot error response [${error.statusCode}]: ${errorMessage}`);
}
throw error;
} }
} }

View file

@ -6,6 +6,8 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { awsApiRequestREST } from './GenericFunctions'; import { awsApiRequestREST } from './GenericFunctions';
@ -130,13 +132,7 @@ export class AwsLambda implements INodeType {
loadOptions: { loadOptions: {
async getFunctions(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> { async getFunctions(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = []; const returnData: INodePropertyOptions[] = [];
const data = await awsApiRequestREST.call(this, 'lambda', 'GET', '/2015-03-31/functions/');
let data;
try {
data = await awsApiRequestREST.call(this, 'lambda', 'GET', '/2015-03-31/functions/');
} catch (err) {
throw new Error(`AWS Error: ${err}`);
}
for (const func of data.Functions!) { for (const func of data.Functions!) {
returnData.push({ returnData.push({
@ -162,9 +158,7 @@ export class AwsLambda implements INodeType {
Qualifier: this.getNodeParameter('qualifier', i) as string, Qualifier: this.getNodeParameter('qualifier', i) as string,
}; };
let responseData; const responseData = await awsApiRequestREST.call(
try {
responseData = await awsApiRequestREST.call(
this, this,
'lambda', 'lambda',
'POST', 'POST',
@ -175,9 +169,6 @@ export class AwsLambda implements INodeType {
'Content-Type': 'application/x-amz-json-1.0', 'Content-Type': 'application/x-amz-json-1.0',
}, },
); );
} catch (err) {
throw new Error(`AWS Error: ${err}`);
}
if (responseData !== null && responseData.errorMessage !== undefined) { if (responseData !== null && responseData.errorMessage !== undefined) {
let errorMessage = responseData.errorMessage; let errorMessage = responseData.errorMessage;
@ -186,7 +177,7 @@ export class AwsLambda implements INodeType {
errorMessage += `\n\nStack trace:\n${responseData.stackTrace}`; errorMessage += `\n\nStack trace:\n${responseData.stackTrace}`;
} }
throw new Error(errorMessage); throw new NodeApiError(this.getNode(), responseData);
} else { } else {
returnData.push({ returnData.push({
result: responseData, result: responseData,

View file

@ -6,6 +6,8 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { awsApiRequestSOAP } from './GenericFunctions'; import { awsApiRequestSOAP } from './GenericFunctions';
@ -107,12 +109,7 @@ export class AwsSns implements INodeType {
// select them easily // select them easily
async getTopics(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> { async getTopics(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = []; const returnData: INodePropertyOptions[] = [];
let data; const data = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=ListTopics');
try {
data = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=ListTopics');
} catch (err) {
throw new Error(`AWS Error: ${err}`);
}
let topics = data.ListTopicsResponse.ListTopicsResult.Topics.member; let topics = data.ListTopicsResponse.ListTopicsResult.Topics.member;
@ -149,12 +146,8 @@ export class AwsSns implements INodeType {
'Message=' + this.getNodeParameter('message', i) as string, 'Message=' + this.getNodeParameter('message', i) as string,
]; ];
let responseData;
try { const responseData = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=Publish&' + params.join('&'));
responseData = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=Publish&' + params.join('&'));
} catch (err) {
throw new Error(`AWS Error: ${err}`);
}
returnData.push({MessageId: responseData.PublishResponse.PublishResult.MessageId} as IDataObject); returnData.push({MessageId: responseData.PublishResponse.PublishResult.MessageId} as IDataObject);
} }

View file

@ -9,6 +9,8 @@ import {
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
IWebhookResponseData, IWebhookResponseData,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -68,12 +70,7 @@ export class AwsSnsTrigger implements INodeType {
// select them easily // select them easily
async getTopics(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> { async getTopics(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = []; const returnData: INodePropertyOptions[] = [];
let data; const data = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=ListTopics');
try {
data = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=ListTopics');
} catch (err) {
throw new Error(`AWS Error: ${err}`);
}
let topics = data.ListTopicsResponse.ListTopicsResult.Topics.member; let topics = data.ListTopicsResponse.ListTopicsResult.Topics.member;
@ -134,7 +131,7 @@ export class AwsSnsTrigger implements INodeType {
const topic = this.getNodeParameter('topic') as string; const topic = this.getNodeParameter('topic') as string;
if (webhookUrl.includes('%20')) { if (webhookUrl.includes('%20')) {
throw new Error('The name of the SNS Trigger Node is not allowed to contain any spaces!'); throw new NodeOperationError(this.getNode(), 'The name of the SNS Trigger Node is not allowed to contain any spaces!');
} }
const params = [ const params = [

View file

@ -22,7 +22,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
function getEndpointForService(service: string, credentials: ICredentialDataDecryptedObject): string { function getEndpointForService(service: string, credentials: ICredentialDataDecryptedObject): string {
@ -40,7 +40,7 @@ function getEndpointForService(service: string, credentials: ICredentialDataDecr
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('aws'); const credentials = this.getCredentials('aws');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
// Concatenate path and instantiate URL object so it parses correctly query strings // Concatenate path and instantiate URL object so it parses correctly query strings
@ -61,17 +61,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message; throw new NodeApiError(this.getNode(), error); // no XML parsing needed
if (error.statusCode === 403) {
if (errorMessage === 'The security token included in the request is invalid.') {
throw new Error('The AWS credentials are not valid!');
} else if (errorMessage.startsWith('The request signature we calculated does not match the signature you provided')) {
throw new Error('The AWS credentials are not valid!');
}
}
throw new Error(`AWS error response [${error.statusCode}]: ${errorMessage}`);
} }
} }
@ -79,7 +69,7 @@ export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions
const response = await awsApiRequest.call(this, service, method, path, body, headers); const response = await awsApiRequest.call(this, service, method, path, body, headers);
try { try {
return JSON.parse(response); return JSON.parse(response);
} catch (e) { } catch (error) {
return response; return response;
} }
} }
@ -95,7 +85,7 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions
resolve(data); resolve(data);
}); });
}); });
} catch (e) { } catch (error) {
return response; return response;
} }
} }

View file

@ -1,7 +1,7 @@
import { URL } from 'url'; import { URL } from 'url';
import { sign } from 'aws4'; import { sign } from 'aws4';
import { OptionsWithUri } from 'request'; import { OptionsWithUri } from 'request';
import { parseString } from 'xml2js'; import { parseString as parseXml } from 'xml2js';
import { import {
IExecuteFunctions, IExecuteFunctions,
@ -11,7 +11,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
function getEndpointForService(service: string, credentials: ICredentialDataDecryptedObject): string { function getEndpointForService(service: string, credentials: ICredentialDataDecryptedObject): string {
@ -31,7 +31,7 @@ function getEndpointForService(service: string, credentials: ICredentialDataDecr
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('aws'); const credentials = this.getCredentials('aws');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
// Concatenate path and instantiate URL object so it parses correctly query strings // Concatenate path and instantiate URL object so it parses correctly query strings
@ -52,17 +52,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message; throw new NodeApiError(this.getNode(), error, { parseXml: true });
if (error.statusCode === 403) {
if (errorMessage === 'The security token included in the request is invalid.') {
throw new Error('The AWS credentials are not valid!');
} else if (errorMessage.startsWith('The request signature we calculated does not match the signature you provided')) {
throw new Error('The AWS credentials are not valid!');
}
}
throw new Error(`AWS error response [${error.statusCode}]: ${errorMessage}`);
} }
} }
@ -70,7 +60,7 @@ export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions
const response = await awsApiRequest.call(this, service, method, path, body, headers); const response = await awsApiRequest.call(this, service, method, path, body, headers);
try { try {
return JSON.parse(response); return JSON.parse(response);
} catch (e) { } catch (error) {
return response; return response;
} }
} }
@ -79,14 +69,14 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions
const response = await awsApiRequest.call(this, service, method, path, body, headers); const response = await awsApiRequest.call(this, service, method, path, body, headers);
try { try {
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
parseString(response, { explicitArray: false }, (err, data) => { parseXml(response, { explicitArray: false }, (err, data) => {
if (err) { if (err) {
return reject(err); return reject(err);
} }
resolve(data); resolve(data);
}); });
}); });
} catch (e) { } catch (error) {
return response; return response;
} }
} }

View file

@ -8,6 +8,8 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -459,11 +461,11 @@ export class AwsRekognition implements INodeType {
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string;
if (items[i].binary === undefined) { if (items[i].binary === undefined) {
throw new Error('No binary data exists on item!'); throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
} }
if ((items[i].binary as IBinaryKeyData)[binaryPropertyName] === undefined) { if ((items[i].binary as IBinaryKeyData)[binaryPropertyName] === undefined) {
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`); throw new NodeOperationError(this.getNode(), `No binary data property "${binaryPropertyName}" does not exists on item!`);
} }
const binaryPropertyData = (items[i].binary as IBinaryKeyData)[binaryPropertyName]; const binaryPropertyData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];
@ -494,7 +496,9 @@ export class AwsRekognition implements INodeType {
body.Image.S3Object.Version = additionalFields.version as string; body.Image.S3Object.Version = additionalFields.version as string;
} }
} }
responseData = await awsApiRequestREST.call(this, 'rekognition', 'POST', '', JSON.stringify(body), {}, { 'X-Amz-Target': action, 'Content-Type': 'application/x-amz-json-1.1' }); responseData = await awsApiRequestREST.call(this, 'rekognition', 'POST', '', JSON.stringify(body), {}, { 'X-Amz-Target': action, 'Content-Type': 'application/x-amz-json-1.1' });
} }
} }
} }

View file

@ -27,6 +27,8 @@ import {
import { import {
IDataObject, IDataObject,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -36,7 +38,7 @@ import {
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string | Buffer | IDataObject, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string | Buffer | IDataObject, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('aws'); const credentials = this.getCredentials('aws');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const endpoint = new URL(((credentials.rekognitionEndpoint as string || '').replace('{region}', credentials.region as string) || `https://${service}.${credentials.region}.amazonaws.com`) + path); const endpoint = new URL(((credentials.rekognitionEndpoint as string || '').replace('{region}', credentials.region as string) || `https://${service}.${credentials.region}.amazonaws.com`) + path);
@ -59,17 +61,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message; throw new NodeApiError(this.getNode(), error);
if (error.statusCode === 403) {
if (errorMessage === 'The security token included in the request is invalid.') {
throw new Error('The AWS credentials are not valid!');
} else if (errorMessage.startsWith('The request signature we calculated does not match the signature you provided')) {
throw new Error('The AWS credentials are not valid!');
}
}
throw new Error(`AWS error response [${error.statusCode}]: ${errorMessage}`);
} }
} }
@ -77,7 +69,7 @@ export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions
const response = await awsApiRequest.call(this, service, method, path, body, query, headers, options, region); const response = await awsApiRequest.call(this, service, method, path, body, query, headers, options, region);
try { try {
return JSON.parse(response); return JSON.parse(response);
} catch (e) { } catch (error) {
return response; return response;
} }
} }
@ -93,8 +85,8 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions
resolve(data); resolve(data);
}); });
}); });
} catch (e) { } catch (error) {
return e; return error;
} }
} }

View file

@ -23,6 +23,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -422,7 +423,7 @@ export class AwsS3 implements INodeType {
const fileName = fileKey.split('/')[fileKey.split('/').length - 1]; const fileName = fileKey.split('/')[fileKey.split('/').length - 1];
if (fileKey.substring(fileKey.length - 1) === '/') { if (fileKey.substring(fileKey.length - 1) === '/') {
throw new Error('Downloding a whole directory is not yet supported, please provide a file key'); throw new NodeOperationError(this.getNode(), 'Downloding a whole directory is not yet supported, please provide a file key');
} }
let region = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', { location: '' }); let region = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', { location: '' });
@ -588,11 +589,11 @@ export class AwsS3 implements INodeType {
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string;
if (items[i].binary === undefined) { if (items[i].binary === undefined) {
throw new Error('No binary data exists on item!'); throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
} }
if ((items[i].binary as IBinaryKeyData)[binaryPropertyName] === undefined) { if ((items[i].binary as IBinaryKeyData)[binaryPropertyName] === undefined) {
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`); throw new NodeOperationError(this.getNode(), `No binary data property "${binaryPropertyName}" does not exists on item!`);
} }
const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName]; const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];

View file

@ -26,13 +26,13 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string | Buffer, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string | Buffer, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('aws'); const credentials = this.getCredentials('aws');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const endpoint = new URL(((credentials.s3Endpoint as string || '').replace('{region}', credentials.region as string) || `https://${service}.${credentials.region}.amazonaws.com`) + path); const endpoint = new URL(((credentials.s3Endpoint as string || '').replace('{region}', credentials.region as string) || `https://${service}.${credentials.region}.amazonaws.com`) + path);
@ -57,17 +57,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message; throw new NodeApiError(this.getNode(), error, { parseXml: true });
if (error.statusCode === 403) {
if (errorMessage === 'The security token included in the request is invalid.') {
throw new Error('The AWS credentials are not valid!');
} else if (errorMessage.startsWith('The request signature we calculated does not match the signature you provided')) {
throw new Error('The AWS credentials are not valid!');
}
}
throw new Error(`AWS error response [${error.statusCode}]: ${errorMessage}`);
} }
} }
@ -75,7 +65,7 @@ export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions
const response = await awsApiRequest.call(this, service, method, path, body, query, headers, options, region); const response = await awsApiRequest.call(this, service, method, path, body, query, headers, options, region);
try { try {
return JSON.parse(response); return JSON.parse(response);
} catch (e) { } catch (error) {
return response; return response;
} }
} }
@ -91,8 +81,8 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions
resolve(data); resolve(data);
}); });
}); });
} catch (e) { } catch (error) {
return e; return error;
} }
} }

View file

@ -9,6 +9,7 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -1098,7 +1099,7 @@ export class AwsSes implements INodeType {
if (toAddresses.length) { if (toAddresses.length) {
setParameter(params, 'Destination.ToAddresses.member', toAddresses); setParameter(params, 'Destination.ToAddresses.member', toAddresses);
} else { } else {
throw new Error('At least one "To Address" has to be added!'); throw new NodeOperationError(this.getNode(), 'At least one "To Address" has to be added!');
} }
if (additionalFields.configurationSetName) { if (additionalFields.configurationSetName) {
@ -1151,7 +1152,7 @@ export class AwsSes implements INodeType {
if (toAddresses.length) { if (toAddresses.length) {
setParameter(params, 'Destination.ToAddresses.member', toAddresses); setParameter(params, 'Destination.ToAddresses.member', toAddresses);
} else { } else {
throw new Error('At least one "To Address" has to be added!'); throw new NodeOperationError(this.getNode(), 'At least one "To Address" has to be added!');
} }
if (additionalFields.configurationSetName) { if (additionalFields.configurationSetName) {

View file

@ -22,7 +22,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -32,7 +32,7 @@ import {
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('aws'); const credentials = this.getCredentials('aws');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const endpoint = new URL(((credentials.sesEndpoint as string || '').replace('{region}', credentials.region as string) || `https://${service}.${credentials.region}.amazonaws.com`) + path); const endpoint = new URL(((credentials.sesEndpoint as string || '').replace('{region}', credentials.region as string) || `https://${service}.${credentials.region}.amazonaws.com`) + path);
@ -52,17 +52,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message; throw new NodeApiError(this.getNode(), error, { parseXml: true });
if (error.statusCode === 403) {
if (errorMessage === 'The security token included in the request is invalid.') {
throw new Error('The AWS credentials are not valid!');
} else if (errorMessage.startsWith('The request signature we calculated does not match the signature you provided')) {
throw new Error('The AWS credentials are not valid!');
}
}
throw new Error(`AWS error response [${error.statusCode}]: ${errorMessage}`);
} }
} }
@ -70,7 +60,7 @@ export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions
const response = await awsApiRequest.call(this, service, method, path, body, headers); const response = await awsApiRequest.call(this, service, method, path, body, headers);
try { try {
return JSON.parse(response); return JSON.parse(response);
} catch (e) { } catch (error) {
return response; return response;
} }
} }
@ -86,7 +76,7 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions
resolve(data); resolve(data);
}); });
}); });
} catch (e) { } catch (error) {
return response; return response;
} }
} }

View file

@ -11,6 +11,8 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -270,8 +272,8 @@ export class AwsSqs implements INodeType {
try { try {
// loads first 1000 queues from SQS // loads first 1000 queues from SQS
data = await awsApiRequestSOAP.call(this, 'sqs', 'GET', `?Action=ListQueues`); data = await awsApiRequestSOAP.call(this, 'sqs', 'GET', `?Action=ListQueues`);
} catch (err) { } catch (error) {
throw new Error(`AWS Error: ${err}`); throw new NodeApiError(this.getNode(), error);
} }
let queues = data.ListQueuesResponse.ListQueuesResult.QueueUrl; let queues = data.ListQueuesResponse.ListQueuesResult.QueueUrl;
@ -349,11 +351,11 @@ export class AwsSqs implements INodeType {
const item = items[i]; const item = items[i];
if (item.binary === undefined) { if (item.binary === undefined) {
throw new Error('No binary data set. So message attribute cannot be added!'); throw new NodeOperationError(this.getNode(), 'No binary data set. So message attribute cannot be added!');
} }
if (item.binary[dataPropertyName] === undefined) { if (item.binary[dataPropertyName] === undefined) {
throw new Error(`The binary property "${dataPropertyName}" does not exist. So message attribute cannot be added!`); throw new NodeOperationError(this.getNode(), `The binary property "${dataPropertyName}" does not exist. So message attribute cannot be added!`);
} }
const binaryData = item.binary[dataPropertyName].data; const binaryData = item.binary[dataPropertyName].data;
@ -374,8 +376,8 @@ export class AwsSqs implements INodeType {
let responseData; let responseData;
try { try {
responseData = await awsApiRequestSOAP.call(this, 'sqs', 'GET', `${queuePath}/?Action=${operation}&` + params.join('&')); responseData = await awsApiRequestSOAP.call(this, 'sqs', 'GET', `${queuePath}/?Action=${operation}&` + params.join('&'));
} catch (err) { } catch (error) {
throw new Error(`AWS Error: ${err}`); throw new NodeApiError(this.getNode(), error);
} }
const result = responseData.SendMessageResponse.SendMessageResult; const result = responseData.SendMessageResponse.SendMessageResult;

View file

@ -11,6 +11,8 @@ import {
IDataObject, IDataObject,
IHookFunctions, IHookFunctions,
IWebhookFunctions, IWebhookFunctions,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -22,7 +24,7 @@ export async function bannerbearApiRequest(this: IExecuteFunctions | IWebhookFun
const credentials = this.getCredentials('bannerbearApi'); const credentials = this.getCredentials('bannerbearApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const options: OptionsWithUri = { const options: OptionsWithUri = {
@ -46,12 +48,7 @@ export async function bannerbearApiRequest(this: IExecuteFunctions | IWebhookFun
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
if (error.response && error.response.body && error.response.body.message) { throw new NodeApiError(this.getNode(), error);
// Try to return the error prettier
//@ts-ignore
throw new Error(`Bannerbear error response [${error.statusCode}]: ${error.response.body.message}`);
}
throw error;
} }
} }

View file

@ -7,6 +7,7 @@ import {
IDataObject, IDataObject,
IHookFunctions, IHookFunctions,
IWebhookFunctions, IWebhookFunctions,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -18,7 +19,7 @@ export async function createDatapoint(this: IExecuteFunctions | IWebhookFunction
const credentials = this.getCredentials('beeminderApi'); const credentials = this.getCredentials('beeminderApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const endpoint = `/users/${credentials.user}/goals/${data.goalName}/datapoints.json`; const endpoint = `/users/${credentials.user}/goals/${data.goalName}/datapoints.json`;
@ -30,7 +31,7 @@ export async function getAllDatapoints(this: IExecuteFunctions | IHookFunctions
const credentials = this.getCredentials('beeminderApi'); const credentials = this.getCredentials('beeminderApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const endpoint = `/users/${credentials.user}/goals/${data.goalName}/datapoints.json`; const endpoint = `/users/${credentials.user}/goals/${data.goalName}/datapoints.json`;
@ -46,7 +47,7 @@ export async function updateDatapoint(this: IExecuteFunctions | IWebhookFunction
const credentials = this.getCredentials('beeminderApi'); const credentials = this.getCredentials('beeminderApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const endpoint = `/users/${credentials.user}/goals/${data.goalName}/datapoints/${data.datapointId}.json`; const endpoint = `/users/${credentials.user}/goals/${data.goalName}/datapoints/${data.datapointId}.json`;
@ -58,7 +59,7 @@ export async function deleteDatapoint(this: IExecuteFunctions | IWebhookFunction
const credentials = this.getCredentials('beeminderApi'); const credentials = this.getCredentials('beeminderApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const endpoint = `/users/${credentials.user}/goals/${data.goalName}/datapoints/${data.datapointId}.json`; const endpoint = `/users/${credentials.user}/goals/${data.goalName}/datapoints/${data.datapointId}.json`;

View file

@ -10,6 +10,7 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -308,7 +309,7 @@ export class Beeminder implements INodeType {
const credentials = this.getCredentials('beeminderApi'); const credentials = this.getCredentials('beeminderApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const endpoint = `/users/${credentials.user}/goals.json`; const endpoint = `/users/${credentials.user}/goals.json`;

View file

@ -11,6 +11,7 @@ import {
IDataObject, IDataObject,
IHookFunctions, IHookFunctions,
IWebhookFunctions, IWebhookFunctions,
NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
const BEEMINDER_URI = 'https://www.beeminder.com/api/v1'; const BEEMINDER_URI = 'https://www.beeminder.com/api/v1';
@ -40,10 +41,7 @@ export async function beeminderApiRequest(this: IExecuteFunctions | IWebhookFunc
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
if (error?.message) { throw new NodeApiError(this.getNode(), error);
throw new Error(`Beeminder error response [${error.statusCode}]: ${error.message}`);
}
throw error;
} }
} }

View file

@ -280,7 +280,7 @@ export class BitbucketTrigger implements INodeType {
} }
try { try {
await bitbucketApiRequest.call(this, 'GET', endpoint); await bitbucketApiRequest.call(this, 'GET', endpoint);
} catch (e) { } catch (error) {
return false; return false;
} }
return true; return true;

View file

@ -5,12 +5,12 @@ import {
IHookFunctions, IHookFunctions,
ILoadOptionsFunctions, ILoadOptionsFunctions,
} from 'n8n-core'; } from 'n8n-core';
import { IDataObject } from 'n8n-workflow'; import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow';
export async function bitbucketApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function bitbucketApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('bitbucketApi'); const credentials = this.getCredentials('bitbucketApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
let options: OptionsWithUri = { let options: OptionsWithUri = {
method, method,
@ -30,8 +30,8 @@ export async function bitbucketApiRequest(this: IHookFunctions | IExecuteFunctio
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (err) { } catch (error) {
throw new Error('Bitbucket Error: ' + err.message); throw new NodeApiError(this.getNode(), error);
} }
} }

View file

@ -10,7 +10,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
@ -32,7 +32,7 @@ export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions |
if (authenticationMethod === 'accessToken') { if (authenticationMethod === 'accessToken') {
const credentials = this.getCredentials('bitlyApi'); const credentials = this.getCredentials('bitlyApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
options.headers = { Authorization: `Bearer ${credentials.accessToken}`}; options.headers = { Authorization: `Bearer ${credentials.accessToken}`};
@ -42,15 +42,7 @@ export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions |
return await this.helpers.requestOAuth2!.call(this, 'bitlyOAuth2Api', options, { tokenType: 'Bearer' }); return await this.helpers.requestOAuth2!.call(this, 'bitlyOAuth2Api', options, { tokenType: 'Bearer' });
} }
} catch(error) { } catch(error) {
throw new NodeApiError(this.getNode(), error);
if (error.response && error.response.body && error.response.body.message) {
// Try to return the error prettier
const errorBody = error.response.body;
throw new Error(`Bitly error response [${error.statusCode}]: ${errorBody.message}`);
}
// Expected error data did not get returned so throw the actual error
throw error;
} }
} }

View file

@ -8,6 +8,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -177,7 +178,7 @@ export class Bitwarden implements INodeType {
const updateFields = this.getNodeParameter('updateFields', i) as CollectionUpdateFields; const updateFields = this.getNodeParameter('updateFields', i) as CollectionUpdateFields;
if (isEmpty(updateFields)) { if (isEmpty(updateFields)) {
throw new Error(`Please enter at least one field to update for the ${resource}.`); throw new NodeOperationError(this.getNode(), `Please enter at least one field to update for the ${resource}.`);
} }
const { groups, externalId } = updateFields; const { groups, externalId } = updateFields;
@ -308,7 +309,7 @@ export class Bitwarden implements INodeType {
const updateFields = this.getNodeParameter('updateFields', i) as GroupUpdateFields; const updateFields = this.getNodeParameter('updateFields', i) as GroupUpdateFields;
if (isEmpty(updateFields)) { if (isEmpty(updateFields)) {
throw new Error(`Please enter at least one field to update for the ${resource}.`); throw new NodeOperationError(this.getNode(), `Please enter at least one field to update for the ${resource}.`);
} }
// set defaults for `name` and `accessAll`, required by Bitwarden but optional in n8n // set defaults for `name` and `accessAll`, required by Bitwarden but optional in n8n
@ -452,7 +453,7 @@ export class Bitwarden implements INodeType {
const updateFields = this.getNodeParameter('updateFields', i) as MemberUpdateFields; const updateFields = this.getNodeParameter('updateFields', i) as MemberUpdateFields;
if (isEmpty(updateFields)) { if (isEmpty(updateFields)) {
throw new Error(`Please enter at least one field to update for the ${resource}.`); throw new NodeOperationError(this.getNode(), `Please enter at least one field to update for the ${resource}.`);
} }
const { accessAll, collections, externalId, type } = updateFields; const { accessAll, collections, externalId, type } = updateFields;

View file

@ -6,6 +6,7 @@ import {
IDataObject, IDataObject,
ILoadOptionsFunctions, ILoadOptionsFunctions,
INodePropertyOptions, INodePropertyOptions,
NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -48,17 +49,7 @@ export async function bitwardenApiRequest(
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error);
if (error.statusCode === 404) {
throw new Error('Bitwarden error response [404]: Not found');
}
if (error?.response?.body?.Message) {
const message = error?.response?.body?.Message;
throw new Error(`Bitwarden error response [${error.statusCode}]: ${message}`);
}
//TODO handle Errors array
throw error;
} }
} }
@ -93,7 +84,7 @@ export async function getAccessToken(
const { access_token } = await this.helpers.request!(options); const { access_token } = await this.helpers.request!(options);
return access_token; return access_token;
} catch (error) { } catch (error) {
throw error; throw new NodeApiError(this.getNode(), error);
} }
} }

View file

@ -9,6 +9,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -217,11 +218,11 @@ export class Box implements INodeType {
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string;
if (items[i].binary === undefined) { if (items[i].binary === undefined) {
throw new Error('No binary data exists on item!'); throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
} }
//@ts-ignore //@ts-ignore
if (items[i].binary[binaryPropertyName] === undefined) { if (items[i].binary[binaryPropertyName] === undefined) {
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`); throw new NodeOperationError(this.getNode(), `No binary data property "${binaryPropertyName}" does not exists on item!`);
} }
const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName]; const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];
@ -248,7 +249,7 @@ export class Box implements INodeType {
const content = this.getNodeParameter('fileContent', i) as string; const content = this.getNodeParameter('fileContent', i) as string;
if (fileName === '') { if (fileName === '') {
throw new Error('File name must be set!'); throw new NodeOperationError(this.getNode(), 'File name must be set!');
} }
attributes['name'] = fileName; attributes['name'] = fileName;

View file

@ -327,7 +327,7 @@ export class BoxTrigger implements INodeType {
try { try {
await boxApiRequest.call(this, 'DELETE', endpoint); await boxApiRequest.call(this, 'DELETE', endpoint);
} catch (e) { } catch (error) {
return false; return false;
} }

View file

@ -12,6 +12,7 @@ import {
import { import {
IDataObject, IDataObject,
IOAuth2Options, IOAuth2Options,
NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function boxApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function boxApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
@ -41,22 +42,7 @@ export async function boxApiRequest(this: IExecuteFunctions | IExecuteSingleFunc
return await this.helpers.requestOAuth2.call(this, 'boxOAuth2Api', options, oAuth2Options); return await this.helpers.requestOAuth2.call(this, 'boxOAuth2Api', options, oAuth2Options);
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error);
let errorMessage;
if (error.response && error.response.body) {
if (error.response.body.context_info && error.response.body.context_info.errors) {
const errors = error.response.body.context_info.errors;
errorMessage = errors.map((e: IDataObject) => e.message);
errorMessage = errorMessage.join('|');
} else if (error.response.body.message) {
errorMessage = error.response.body.message;
}
throw new Error(`Box error response [${error.statusCode}]: ${errorMessage}`);
}
throw error;
} }
} }

View file

@ -10,14 +10,14 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function brandfetchApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function brandfetchApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
try { try {
const credentials = this.getCredentials('brandfetchApi'); const credentials = this.getCredentials('brandfetchApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
let options: OptionsWithUri = { let options: OptionsWithUri = {
headers: { headers: {
@ -46,20 +46,12 @@ export async function brandfetchApiRequest(this: IHookFunctions | IExecuteFuncti
const response = await this.helpers.request!(options); const response = await this.helpers.request!(options);
if (response.statusCode && response.statusCode !== 200) { if (response.statusCode && response.statusCode !== 200) {
throw new Error(`Brandfetch error response [${response.statusCode}]: ${response.response}`); throw new NodeApiError(this.getNode(), response);
} }
return response; return response;
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error);
if (error.response && error.response.body && error.response.body.message) {
// Try to return the error prettier
const errorBody = error.response.body;
throw new Error(`Brandfetch error response [${error.statusCode}]: ${errorBody.message}`);
}
// Expected error data did not get returned so throw the actual error
throw error;
} }
} }

View file

@ -7,6 +7,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -153,7 +154,7 @@ export class Bubble implements INodeType {
const filter = options.filtersJson as string; const filter = options.filtersJson as string;
const data = validateJSON(filter); const data = validateJSON(filter);
if (data === undefined) { if (data === undefined) {
throw new Error('Filters must be a valid JSON'); throw new NodeOperationError(this.getNode(), 'Filters must be a valid JSON');
} }
qs.constraints = JSON.stringify(data); qs.constraints = JSON.stringify(data);
} }

View file

@ -6,6 +6,7 @@ import {
import { import {
IDataObject, IDataObject,
ILoadOptionsFunctions, ILoadOptionsFunctions,
NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -57,11 +58,7 @@ export async function bubbleApiRequest(
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
if (error?.response?.body?.body?.message) { throw new NodeApiError(this.getNode(), error);
const errorMessage = error.response.body.body.message;
throw new Error(`Bubble.io error response [${error.statusCode}]: ${errorMessage}`);
}
throw error;
} }
} }

View file

@ -122,7 +122,7 @@ export class CalendlyTrigger implements INodeType {
try { try {
await calendlyApiRequest.call(this, 'DELETE', endpoint); await calendlyApiRequest.call(this, 'DELETE', endpoint);
} catch (e) { } catch (error) {
return false; return false;
} }

View file

@ -8,7 +8,9 @@ import {
import { import {
IDataObject, IDataObject,
IHookFunctions, IHookFunctions,
IWebhookFunctions IWebhookFunctions,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function calendlyApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function calendlyApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
@ -16,7 +18,7 @@ export async function calendlyApiRequest(this: IExecuteFunctions | IWebhookFunct
const credentials = this.getCredentials('calendlyApi'); const credentials = this.getCredentials('calendlyApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const endpoint = 'https://calendly.com/api/v1'; const endpoint = 'https://calendly.com/api/v1';
@ -42,10 +44,6 @@ export async function calendlyApiRequest(this: IExecuteFunctions | IWebhookFunct
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
if (error.response) { throw new NodeApiError(this.getNode(), error);
const errorMessage = error.response.body.message || error.response.body.description || error.message;
throw new Error(`Calendly error response [${error.statusCode}]: ${errorMessage}`);
}
throw error;
} }
} }

View file

@ -4,6 +4,8 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeApiError,
NodeOperationError,
NodeParameterValue, NodeParameterValue,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -489,7 +491,7 @@ export class Chargebee implements INodeType {
const credentials = this.getCredentials('chargebeeApi'); const credentials = this.getCredentials('chargebeeApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const baseUrl = `https://${credentials.accountName}.chargebee.com/api/v2`; const baseUrl = `https://${credentials.accountName}.chargebee.com/api/v2`;
@ -531,7 +533,7 @@ export class Chargebee implements INodeType {
endpoint = `customers`; endpoint = `customers`;
} else { } else {
throw new Error(`The operation "${operation}" is not known!`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`);
} }
} else if (resource === 'invoice') { } else if (resource === 'invoice') {
@ -569,7 +571,7 @@ export class Chargebee implements INodeType {
const invoiceId = this.getNodeParameter('invoiceId', i) as string; const invoiceId = this.getNodeParameter('invoiceId', i) as string;
endpoint = `invoices/${invoiceId.trim()}/pdf`; endpoint = `invoices/${invoiceId.trim()}/pdf`;
} else { } else {
throw new Error(`The operation "${operation}" is not known!`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`);
} }
} else if (resource === 'subscription') { } else if (resource === 'subscription') {
@ -595,10 +597,10 @@ export class Chargebee implements INodeType {
endpoint = `subscriptions/${subscriptionId.trim()}/delete`; endpoint = `subscriptions/${subscriptionId.trim()}/delete`;
} else { } else {
throw new Error(`The operation "${operation}" is not known!`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`);
} }
} else { } else {
throw new Error(`The resource "${resource}" is not known!`); throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`);
} }
const options = { const options = {
@ -613,7 +615,13 @@ export class Chargebee implements INodeType {
json: true, json: true,
}; };
const responseData = await this.helpers.request!(options); let responseData;
try {
responseData = await this.helpers.request!(options);
} catch (error) {
throw new NodeApiError(this.getNode(), error);
}
if (resource === 'invoice' && operation === 'list') { if (resource === 'invoice' && operation === 'list') {
responseData.list.forEach((data: IDataObject) => { responseData.list.forEach((data: IDataObject) => {

View file

@ -10,13 +10,13 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function circleciApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function circleciApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('circleCiApi'); const credentials = this.getCredentials('circleCiApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
let options: OptionsWithUri = { let options: OptionsWithUri = {
headers: { headers: {
@ -35,14 +35,9 @@ export async function circleciApiRequest(this: IHookFunctions | IExecuteFunction
} }
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (err) { } catch (error) {
if (err.response && err.response.body && err.response.body.message) { throw new NodeApiError(this.getNode(), error);
// Try to return the error prettier
throw new Error(`CircleCI error response [${err.statusCode}]: ${err.response.body.message}`);
} }
// If that data does not exist for some reason return the actual error
throw err; }
} }
/** /**

View file

@ -9,14 +9,12 @@ import {
ILoadOptionsFunctions, ILoadOptionsFunctions,
} from 'n8n-core'; } from 'n8n-core';
import { import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow';
IDataObject,
} from 'n8n-workflow';
export async function clearbitApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, api: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function clearbitApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, api: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('clearbitApi'); const credentials = this.getCredentials('clearbitApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
let options: OptionsWithUri = { let options: OptionsWithUri = {
headers: { Authorization: `Bearer ${credentials.apiKey}` }, headers: { Authorization: `Bearer ${credentials.apiKey}` },
@ -33,17 +31,6 @@ export async function clearbitApiRequest(this: IHookFunctions | IExecuteFunction
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
if (error.statusCode === 401) { throw new NodeApiError(this.getNode(), error);
// Return a clear error
throw new Error('The Clearbit credentials are not valid!');
}
if (error.response.body && error.response.body.error && error.response.body.error.message) {
// Try to return the error prettier
throw new Error(`Clearbit Error [${error.statusCode}]: ${error.response.body.error.message}`);
}
// If that data does not exist for some reason return the actual error
throw new Error('Clearbit Error: ' + error.message);
} }
} }

View file

@ -9,6 +9,7 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -719,14 +720,14 @@ export class ClickUp implements INodeType {
}; };
if (type === 'number' || type === 'currency') { if (type === 'number' || type === 'currency') {
if (!additionalFields.unit) { if (!additionalFields.unit) {
throw new Error('Unit field must be set'); throw new NodeOperationError(this.getNode(), 'Unit field must be set');
} }
} }
if (type === 'number' || type === 'percentaje' if (type === 'number' || type === 'percentaje'
|| type === 'automatic' || type === 'currency') { || type === 'automatic' || type === 'currency') {
if (additionalFields.stepsStart === undefined if (additionalFields.stepsStart === undefined
|| !additionalFields.stepsEnd === undefined) { || !additionalFields.stepsEnd === undefined) {
throw new Error('Steps start and steps end fields must be set'); throw new NodeOperationError(this.getNode(), 'Steps start and steps end fields must be set');
} }
} }
if (additionalFields.unit) { if (additionalFields.unit) {
@ -845,7 +846,7 @@ export class ClickUp implements INodeType {
if (additionalFields.customFieldsJson) { if (additionalFields.customFieldsJson) {
const customFields = validateJSON(additionalFields.customFieldsJson as string); const customFields = validateJSON(additionalFields.customFieldsJson as string);
if (customFields === undefined) { if (customFields === undefined) {
throw new Error('Custom Fields: Invalid JSON'); throw new NodeOperationError(this.getNode(), 'Custom Fields: Invalid JSON');
} }
body.custom_fields = customFields; body.custom_fields = customFields;
} }
@ -1042,7 +1043,7 @@ export class ClickUp implements INodeType {
if (jsonParse === true) { if (jsonParse === true) {
body.value = validateJSON(body.value); body.value = validateJSON(body.value);
if (body.value === undefined) { if (body.value === undefined) {
throw new Error('Value is invalid JSON!'); throw new NodeOperationError(this.getNode(), 'Value is invalid JSON!');
} }
} else { } else {
//@ts-ignore //@ts-ignore
@ -1213,7 +1214,7 @@ export class ClickUp implements INodeType {
if (responseData.data) { if (responseData.data) {
responseData = responseData.data; responseData = responseData.data;
} else { } else {
throw new Error('There seems to be nothing to stop.'); throw new NodeOperationError(this.getNode(), 'There seems to be nothing to stop.');
} }
} }
if (operation === 'delete') { if (operation === 'delete') {
@ -1233,7 +1234,7 @@ export class ClickUp implements INodeType {
if (tagsUi) { if (tagsUi) {
const tags = (tagsUi as IDataObject).tagsValues as IDataObject[]; const tags = (tagsUi as IDataObject).tagsValues as IDataObject[];
if (tags === undefined) { if (tags === undefined) {
throw new Error('At least one tag must be set'); throw new NodeOperationError(this.getNode(), 'At least one tag must be set');
} }
body.tags = tags; body.tags = tags;
} }

View file

@ -13,8 +13,10 @@ import {
import { import {
IDataObject, IDataObject,
IOAuth2Options, IOAuth2Options,
NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function clickupApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function clickupApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const options: OptionsWithUri = { const options: OptionsWithUri = {
headers: { headers: {
@ -34,15 +36,10 @@ export async function clickupApiRequest(this: IHookFunctions | IExecuteFunctions
const credentials = this.getCredentials('clickUpApi'); const credentials = this.getCredentials('clickUpApi');
if (credentials === undefined) { options.headers!['Authorization'] = credentials?.accessToken;
throw new Error('No credentials got returned!');
}
options.headers!['Authorization'] = credentials.accessToken;
return await this.helpers.request!(options); return await this.helpers.request!(options);
} else { } else {
const oAuth2Options: IOAuth2Options = { const oAuth2Options: IOAuth2Options = {
keepBearer: false, keepBearer: false,
tokenType: 'Bearer', tokenType: 'Bearer',
@ -50,15 +47,9 @@ export async function clickupApiRequest(this: IHookFunctions | IExecuteFunctions
// @ts-ignore // @ts-ignore
return await this.helpers.requestOAuth2!.call(this, 'clickUpOAuth2Api', options, oAuth2Options); return await this.helpers.requestOAuth2!.call(this, 'clickUpOAuth2Api', options, oAuth2Options);
} }
} catch(error) { } catch(error) {
let errorMessage = error; throw new NodeApiError(this.getNode(), error);
if (error.err) {
errorMessage = error.err;
} }
throw new Error('ClickUp Error: ' + errorMessage);
}
} }
export async function clickupApiRequestAllItems(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function clickupApiRequestAllItems(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any

View file

@ -9,7 +9,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function clockifyApiRequest(this: ILoadOptionsFunctions | IPollFunctions | IExecuteFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function clockifyApiRequest(this: ILoadOptionsFunctions | IPollFunctions | IExecuteFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
@ -17,7 +17,7 @@ export async function clockifyApiRequest(this: ILoadOptionsFunctions | IPollFunc
const credentials = this.getCredentials('clockifyApi'); const credentials = this.getCredentials('clockifyApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const BASE_URL = 'https://api.clockify.me/api/v1'; const BASE_URL = 'https://api.clockify.me/api/v1';
@ -36,20 +36,9 @@ export async function clockifyApiRequest(this: ILoadOptionsFunctions | IPollFunc
}; };
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error);
let errorMessage = error.message;
if (error.response.body && error.response.body.message) {
errorMessage = `[${error.statusCode}] ${error.response.body.message}`;
}
throw new Error('Clockify Error: ' + errorMessage);
} }
} }

View file

@ -3,14 +3,14 @@ import {
IExecuteSingleFunctions, IExecuteSingleFunctions,
ILoadOptionsFunctions, ILoadOptionsFunctions,
} from 'n8n-core'; } from 'n8n-core';
import { IDataObject } from 'n8n-workflow'; import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow';
import { OptionsWithUri } from 'request'; import { OptionsWithUri } from 'request';
export async function cockpitApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function cockpitApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('cockpitApi'); const credentials = this.getCredentials('cockpitApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials available.'); throw new NodeOperationError(this.getNode(), 'No credentials available.');
} }
let options: OptionsWithUri = { let options: OptionsWithUri = {
@ -36,12 +36,7 @@ export async function cockpitApiRequest(this: IExecuteFunctions | IExecuteSingle
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
let errorMessage = error.message; throw new NodeApiError(this.getNode(), error);
if (error.error) {
errorMessage = error.error.message || error.error.error;
}
throw new Error(`Cockpit error [${error.statusCode}]: ` + errorMessage);
} }
} }

View file

@ -8,6 +8,8 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
codaApiRequest, codaApiRequest,
@ -338,8 +340,8 @@ export class Coda implements INodeType {
responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs); responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs);
responseData = responseData.items; responseData = responseData.items;
} }
} catch (err) { } catch (error) {
throw new Error(`Coda Error: ${err.message}`); throw new NodeApiError(this.getNode(), error);
} }
if (options.rawData === true) { if (options.rawData === true) {
@ -540,8 +542,8 @@ export class Coda implements INodeType {
responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs); responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs);
responseData = responseData.items; responseData = responseData.items;
} }
} catch (err) { } catch (error) {
throw new Error(`Coda Error: ${err.message}`); throw new NodeApiError(this.getNode(), error);
} }
if (options.rawData === true) { if (options.rawData === true) {

View file

@ -4,12 +4,12 @@ import {
IExecuteSingleFunctions, IExecuteSingleFunctions,
ILoadOptionsFunctions, ILoadOptionsFunctions,
} from 'n8n-core'; } from 'n8n-core';
import { IDataObject } from 'n8n-workflow'; import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow';
export async function codaApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function codaApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('codaApi'); const credentials = this.getCredentials('codaApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
let options: OptionsWithUri = { let options: OptionsWithUri = {
@ -28,12 +28,7 @@ export async function codaApiRequest(this: IExecuteFunctions | IExecuteSingleFun
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
let errorMessage = error.message; throw new NodeApiError(this.getNode(), error);
if (error.response.body) {
errorMessage = error.response.body.message || error.response.body.Message || error.message;
}
throw new Error('Coda Error: ' + errorMessage);
} }
} }

View file

@ -9,7 +9,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject, NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function coinGeckoApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, export async function coinGeckoApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string,
@ -37,8 +37,7 @@ export async function coinGeckoApiRequest(this: IExecuteFunctions | IExecuteSing
return await this.helpers.request.call(this, options); return await this.helpers.request.call(this, options);
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error);
throw error;
} }
} }

View file

@ -8,6 +8,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import * as fflate from 'fflate'; import * as fflate from 'fflate';
@ -216,11 +217,11 @@ export class Compression implements INodeType {
for (const [index, binaryPropertyName] of binaryPropertyNames.entries()) { for (const [index, binaryPropertyName] of binaryPropertyNames.entries()) {
if (items[i].binary === undefined) { if (items[i].binary === undefined) {
throw new Error('No binary data exists on item!'); throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
} }
//@ts-ignore //@ts-ignore
if (items[i].binary[binaryPropertyName] === undefined) { if (items[i].binary[binaryPropertyName] === undefined) {
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`); throw new NodeOperationError(this.getNode(), `No binary data property "${binaryPropertyName}" does not exists on item!`);
} }
const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName]; const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];
@ -270,11 +271,11 @@ export class Compression implements INodeType {
for (const [index, binaryPropertyName] of binaryPropertyNames.entries()) { for (const [index, binaryPropertyName] of binaryPropertyNames.entries()) {
if (items[i].binary === undefined) { if (items[i].binary === undefined) {
throw new Error('No binary data exists on item!'); throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
} }
//@ts-ignore //@ts-ignore
if (items[i].binary[binaryPropertyName] === undefined) { if (items[i].binary[binaryPropertyName] === undefined) {
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`); throw new NodeOperationError(this.getNode(), `No binary data property "${binaryPropertyName}" does not exists on item!`);
} }
const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName]; const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];

View file

@ -9,14 +9,14 @@ import {
} from 'request'; } from 'request';
import { import {
IDataObject, IDataObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function contentfulApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function contentfulApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('contentfulApi'); const credentials = this.getCredentials('contentfulApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const source = this.getNodeParameter('source', 0) as string; const source = this.getNodeParameter('source', 0) as string;
@ -39,7 +39,7 @@ export async function contentfulApiRequest(this: IExecuteFunctions | IExecuteSin
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
throw new Error(`Contentful error response [${error.statusCode}]: ${error.error.message}`); throw new NodeApiError(this.getNode(), error);
} }
} }

View file

@ -10,7 +10,9 @@ import {
import { import {
IDataObject, IDataObject,
IHookFunctions IHookFunctions,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function convertKitApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, export async function convertKitApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions,
@ -19,7 +21,7 @@ export async function convertKitApiRequest(this: IExecuteFunctions | IExecuteSin
const credentials = this.getCredentials('convertKitApi'); const credentials = this.getCredentials('convertKitApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
let options: OptionsWithUri = { let options: OptionsWithUri = {
@ -51,17 +53,8 @@ export async function convertKitApiRequest(this: IExecuteFunctions | IExecuteSin
} }
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error);
let errorMessage = error;
if (error.response && error.response.body && error.response.body.message) {
errorMessage = error.response.body.message;
}
throw new Error(`ConvertKit error response: ${errorMessage}`);
} }
} }

View file

@ -116,7 +116,7 @@ export class CopperTrigger implements INodeType {
const endpoint = `/webhooks/${webhookData.webhookId}`; const endpoint = `/webhooks/${webhookData.webhookId}`;
try { try {
await copperApiRequest.call(this, 'GET', endpoint); await copperApiRequest.call(this, 'GET', endpoint);
} catch (err) { } catch (error) {
return false; return false;
} }
return true; return true;

View file

@ -17,6 +17,7 @@ import {
import { import {
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject,
IDataObject, IDataObject,
NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -72,12 +73,7 @@ export async function copperApiRequest(
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
let errorMessage = error.message; throw new NodeApiError(this.getNode(), error);
if (error.response.body?.message) {
errorMessage = error.response.body.message;
}
throw new Error(`Copper error response [${error.statusCode}]: ${errorMessage}`);
} }
} }

View file

@ -23,6 +23,7 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -231,13 +232,13 @@ export class Cortex implements INodeType {
const item = items[i]; const item = items[i];
if (item.binary === undefined) { if (item.binary === undefined) {
throw new Error('No binary data exists on item!'); throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
} }
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
if (item.binary[binaryPropertyName] === undefined) { if (item.binary[binaryPropertyName] === undefined) {
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`); throw new NodeOperationError(this.getNode(), `No binary data property "${binaryPropertyName}" does not exists on item!`);
} }
const fileBufferData = Buffer.from(item.binary[binaryPropertyName].data, BINARY_ENCODING); const fileBufferData = Buffer.from(item.binary[binaryPropertyName].data, BINARY_ENCODING);
@ -386,13 +387,13 @@ export class Cortex implements INodeType {
const item = items[i]; const item = items[i];
if (item.binary === undefined) { if (item.binary === undefined) {
throw new Error('No binary data exists on item!'); throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
} }
const binaryPropertyName = artifactvalue.binaryProperty as string; const binaryPropertyName = artifactvalue.binaryProperty as string;
if (item.binary[binaryPropertyName] === undefined) { if (item.binary[binaryPropertyName] === undefined) {
throw new Error(`No binary data property '${binaryPropertyName}' does not exists on item!`); throw new NodeOperationError(this.getNode(), `No binary data property '${binaryPropertyName}' does not exists on item!`);
} }
const binaryData = item.binary[binaryPropertyName] as IBinaryData; const binaryData = item.binary[binaryPropertyName] as IBinaryData;
@ -415,12 +416,12 @@ export class Cortex implements INodeType {
const item = items[i]; const item = items[i];
if (item.binary === undefined) { if (item.binary === undefined) {
throw new Error('No binary data exists on item!'); throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
} }
const binaryPropertyName = (body.data as IDataObject).binaryPropertyName as string; const binaryPropertyName = (body.data as IDataObject).binaryPropertyName as string;
if (item.binary[binaryPropertyName] === undefined) { if (item.binary[binaryPropertyName] === undefined) {
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`); throw new NodeOperationError(this.getNode(), `No binary data property "${binaryPropertyName}" does not exists on item!`);
} }
const fileBufferData = Buffer.from(item.binary[binaryPropertyName].data, BINARY_ENCODING); const fileBufferData = Buffer.from(item.binary[binaryPropertyName].data, BINARY_ENCODING);

View file

@ -16,7 +16,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import * as moment from 'moment'; import * as moment from 'moment';
@ -26,7 +26,7 @@ export async function cortexApiRequest(this: IHookFunctions | IExecuteFunctions
const credentials = this.getCredentials('cortexApi'); const credentials = this.getCredentials('cortexApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const headerWithAuthentication = Object.assign({}, { Authorization: ` Bearer ${credentials.cortexApiKey}` }); const headerWithAuthentication = Object.assign({}, { Authorization: ` Bearer ${credentials.cortexApiKey}` });
@ -53,10 +53,7 @@ export async function cortexApiRequest(this: IHookFunctions | IExecuteFunctions
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
if (error.error) { throw new NodeApiError(this.getNode(), error);
const errorMessage = `Cortex error response [${error.statusCode}]: ${error.error.message}`;
throw new Error(errorMessage);
} else throw error;
} }
} }

View file

@ -4,6 +4,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -188,7 +189,7 @@ export class CrateDb implements INodeType {
const credentials = this.getCredentials('crateDb'); const credentials = this.getCredentials('crateDb');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const pgp = pgPromise(); const pgp = pgPromise();
@ -262,7 +263,7 @@ export class CrateDb implements INodeType {
updateKeyValue = item.json[updateKey] as string | number; updateKeyValue = item.json[updateKey] as string | number;
if (updateKeyValue === undefined) { if (updateKeyValue === undefined) {
throw new Error('No value found for update key!'); throw new NodeOperationError(this.getNode(), 'No value found for update key!');
} }
updatedKeys.push(updateKeyValue as string); updatedKeys.push(updateKeyValue as string);
@ -277,7 +278,7 @@ export class CrateDb implements INodeType {
returnItems = this.helpers.returnJsonArray(getItemCopy(items, columns) as IDataObject[]); returnItems = this.helpers.returnJsonArray(getItemCopy(items, columns) as IDataObject[]);
} else { } else {
await pgp.end(); await pgp.end();
throw new Error(`The operation "${operation}" is not supported!`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not supported!`);
} }
// Close the connection // Close the connection

View file

@ -6,6 +6,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
customerIoApiRequest, customerIoApiRequest,
@ -131,7 +132,7 @@ export class CustomerIo implements INodeType {
Object.assign(body, JSON.parse(additionalFieldsJson)); Object.assign(body, JSON.parse(additionalFieldsJson));
} else { } else {
throw new Error('Additional fields must be a valid JSON'); throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON');
} }
} }
} else { } else {
@ -175,7 +176,7 @@ export class CustomerIo implements INodeType {
Object.assign(body, JSON.parse(additionalFieldsJson)); Object.assign(body, JSON.parse(additionalFieldsJson));
} else { } else {
throw new Error('Additional fields must be a valid JSON'); throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON');
} }
} }
} else { } else {
@ -238,7 +239,7 @@ export class CustomerIo implements INodeType {
if (validateJSON(additionalFieldsJson) !== undefined) { if (validateJSON(additionalFieldsJson) !== undefined) {
Object.assign(body, JSON.parse(additionalFieldsJson)); Object.assign(body, JSON.parse(additionalFieldsJson));
} else { } else {
throw new Error('Additional fields must be a valid JSON'); throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON');
} }
} }
} else { } else {
@ -283,7 +284,7 @@ export class CustomerIo implements INodeType {
Object.assign(body, JSON.parse(additionalFieldsJson)); Object.assign(body, JSON.parse(additionalFieldsJson));
} else { } else {
throw new Error('Additional fields must be a valid JSON'); throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON');
} }
} }
} else { } else {

View file

@ -308,7 +308,7 @@ export class CustomerIoTrigger implements INodeType {
const endpoint = `/reporting_webhooks/${webhookData.webhookId}`; const endpoint = `/reporting_webhooks/${webhookData.webhookId}`;
try { try {
await customerIoApiRequest.call(this, 'DELETE', endpoint, {}, 'beta'); await customerIoApiRequest.call(this, 'DELETE', endpoint, {}, 'beta');
} catch (e) { } catch (error) {
return false; return false;
} }
delete webhookData.webhookId; delete webhookData.webhookId;

View file

@ -9,7 +9,7 @@ import {
} from 'request'; } from 'request';
import { import {
IDataObject, IDataObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -20,7 +20,7 @@ export async function customerIoApiRequest(this: IHookFunctions | IExecuteFuncti
const credentials = this.getCredentials('customerIoApi'); const credentials = this.getCredentials('customerIoApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
query = query || {}; query = query || {};
@ -51,19 +51,7 @@ export async function customerIoApiRequest(this: IHookFunctions | IExecuteFuncti
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
if (error.statusCode === 401) { throw new NodeApiError(this.getNode(), error);
// Return a clear error
throw new Error('The Customer.io credentials are not valid!');
}
if (error.response && error.response.body && error.response.body.error_code) {
// Try to return the error prettier
const errorBody = error.response.body;
throw new Error(`Customer.io error response [${errorBody.error_code}]: ${errorBody.description}`);
}
// Expected error data did not get returned so throw the actual error
throw error;
} }
} }

View file

@ -9,6 +9,7 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -244,7 +245,7 @@ export class DateTime implements INodeType {
continue; continue;
} }
if (options.fromFormat === undefined && !moment(currentDate as string | number).isValid()) { if (options.fromFormat === undefined && !moment(currentDate as string | number).isValid()) {
throw new Error('The date input format could not be recognized. Please set the "From Format" field'); throw new NodeOperationError(this.getNode(), 'The date input format could not be recognized. Please set the "From Format" field');
} }
if (Number.isInteger(currentDate as unknown as number)) { if (Number.isInteger(currentDate as unknown as number)) {

View file

@ -9,7 +9,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function deepLApiRequest( export async function deepLApiRequest(
@ -45,7 +45,7 @@ export async function deepLApiRequest(
const credentials = this.getCredentials('deepLApi'); const credentials = this.getCredentials('deepLApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
options.qs.auth_key = credentials.apiKey; options.qs.auth_key = credentials.apiKey;
@ -53,10 +53,6 @@ export async function deepLApiRequest(
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
if (error?.response?.body?.message) { throw new NodeApiError(this.getNode(), error);
// Try to return the error prettier
throw new Error(`DeepL error response [${error.statusCode}]: ${error.response.body.message}`);
}
throw error;
} }
} }

View file

@ -10,14 +10,14 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function demioApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function demioApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
try { try {
const credentials = this.getCredentials('demioApi'); const credentials = this.getCredentials('demioApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
let options: OptionsWithUri = { let options: OptionsWithUri = {
headers: { headers: {
@ -35,14 +35,6 @@ export async function demioApiRequest(this: IHookFunctions | IExecuteFunctions |
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error);
if (error.response && error.response.body && error.response.body.message) {
// Try to return the error prettier
const errorBody = error.response.body;
throw new Error(`Demio error response [${error.statusCode}]: ${errorBody.message}`);
}
// Expected error data did not get returned so throw the actual error
throw error;
} }
} }

View file

@ -5,6 +5,8 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export class Discord implements INodeType { export class Discord implements INodeType {
@ -86,15 +88,14 @@ export class Discord implements INodeType {
}, get(error, 'response.body.retry_after', 150)); }, get(error, 'response.body.retry_after', 150));
}); });
} else { } else {
// If it's another error code then return the JSON response throw new NodeApiError(this.getNode(), error);
throw error;
} }
} }
} while (--maxTries); } while (--maxTries);
if (maxTries <= 0) { if (maxTries <= 0) {
throw new Error('Could not send message. Max. amount of rate-limit retries got reached.'); throw new NodeApiError(this.getNode(), { request: options }, { message: 'Could not send message. Max. amount of rate-limit retries got reached.' });
} }
returnData.push({success: true}); returnData.push({success: true});

View file

@ -9,7 +9,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject, NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function discourseApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise<any> { // tslint:disable-line:no-any export async function discourseApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise<any> { // tslint:disable-line:no-any
@ -35,15 +35,7 @@ export async function discourseApiRequest(this: IExecuteFunctions | IExecuteSing
//@ts-ignore //@ts-ignore
return await this.helpers.request.call(this, options); return await this.helpers.request.call(this, options);
} catch (error) { } catch (error) {
if (error.response && error.response.body && error.response.body.errors) { throw new NodeApiError(this.getNode(), error);
const errors = error.response.body.errors;
// Try to return the error prettier
throw new Error(
`Discourse error response [${error.statusCode}]: ${errors.join('|')}`,
);
}
throw error;
} }
} }

View file

@ -6,6 +6,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { disqusApiRequest, disqusApiRequestAllItems } from './GenericFunctions'; import { disqusApiRequest, disqusApiRequestAllItems } from './GenericFunctions';
@ -771,11 +772,11 @@ export class Disqus implements INodeType {
} }
} else { } else {
throw new Error(`The operation "${operation}" is not known!`); throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`);
} }
} else { } else {
throw new Error(`The resource "${resource}" is not known!`); throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`);
} }
} }

View file

@ -5,7 +5,7 @@ import {
IHookFunctions, IHookFunctions,
ILoadOptionsFunctions, ILoadOptionsFunctions,
} from 'n8n-core'; } from 'n8n-core';
import { IDataObject } from 'n8n-workflow'; import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow';
export async function disqusApiRequest( export async function disqusApiRequest(
this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
@ -19,7 +19,7 @@ export async function disqusApiRequest(
const credentials = this.getCredentials('disqusApi') as IDataObject; const credentials = this.getCredentials('disqusApi') as IDataObject;
qs.api_key = credentials.accessToken; qs.api_key = credentials.accessToken;
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
// Convert to query string into a format the API can read // Convert to query string into a format the API can read
@ -46,21 +46,9 @@ export async function disqusApiRequest(
delete options.body; delete options.body;
} }
try { try {
const result = await this.helpers.request!(options); return await this.helpers.request!(options);
return result;
} catch (error) { } catch (error) {
if (error.statusCode === 401) { throw new NodeApiError(this.getNode(), error);
// Return a clear error
throw new Error('The Disqus credentials are not valid!');
}
if (error.error && error.error.error_summary) {
// Try to return the error prettier
throw new Error(`Disqus error response [${error.statusCode}]: ${error.error.error_summary}`);
}
// If that data does not exist for some reason return the actual error
throw error;
} }
} }

View file

@ -8,7 +8,9 @@ import {
import { import {
IDataObject, IDataObject,
IHookFunctions, IHookFunctions,
IWebhookFunctions IWebhookFunctions,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function driftApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function driftApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
@ -36,7 +38,7 @@ export async function driftApiRequest(this: IExecuteFunctions | IWebhookFunction
const credentials = this.getCredentials('driftApi'); const credentials = this.getCredentials('driftApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
options.headers!['Authorization'] = `Bearer ${credentials.accessToken}`; options.headers!['Authorization'] = `Bearer ${credentials.accessToken}`;
@ -46,11 +48,6 @@ export async function driftApiRequest(this: IExecuteFunctions | IWebhookFunction
return await this.helpers.requestOAuth2!.call(this, 'driftOAuth2Api', options); return await this.helpers.requestOAuth2!.call(this, 'driftOAuth2Api', options);
} }
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error);
if (error.response && error.response.body && error.response.body.error) {
const errorMessage = error.response.body.error.message;
throw new Error(`Drift error response [${error.statusCode}]: ${errorMessage}`);
}
throw error;
} }
} }

View file

@ -8,6 +8,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -849,13 +850,13 @@ export class Dropbox implements INodeType {
const item = items[i]; const item = items[i];
if (item.binary === undefined) { if (item.binary === undefined) {
throw new Error('No binary data exists on item!'); throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
} }
const propertyNameUpload = this.getNodeParameter('binaryPropertyName', i) as string; const propertyNameUpload = this.getNodeParameter('binaryPropertyName', i) as string;
if (item.binary[propertyNameUpload] === undefined) { if (item.binary[propertyNameUpload] === undefined) {
throw new Error(`No binary data property "${propertyNameUpload}" does not exists on item!`); throw new NodeOperationError(this.getNode(), `No binary data property "${propertyNameUpload}" does not exists on item!`);
} }
body = Buffer.from(item.binary[propertyNameUpload].data, BINARY_ENCODING); body = Buffer.from(item.binary[propertyNameUpload].data, BINARY_ENCODING);
@ -980,7 +981,7 @@ export class Dropbox implements INodeType {
endpoint = 'https://api.dropboxapi.com/2/files/move_v2'; endpoint = 'https://api.dropboxapi.com/2/files/move_v2';
} }
} else { } else {
throw new Error(`The resource "${resource}" is not known!`); throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`);
} }
if (resource === 'file' && operation === 'download') { if (resource === 'file' && operation === 'download') {

View file

@ -8,7 +8,7 @@ import {
} from 'request'; } from 'request';
import { import {
IDataObject, IDataObject, NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
/** /**
@ -51,20 +51,7 @@ export async function dropboxApiRequest(this: IHookFunctions | IExecuteFunctions
return await this.helpers.requestOAuth2.call(this, 'dropboxOAuth2Api', options); return await this.helpers.requestOAuth2.call(this, 'dropboxOAuth2Api', options);
} }
} catch (error) { } catch (error) {
if (error.statusCode === 401) { throw new NodeApiError(this.getNode(), error);
// Return a clear error
throw new Error('The Dropbox credentials are not valid!');
}
if (error.error && error.error.error_summary) {
// Try to return the error prettier
throw new Error(
`Dropbox error response [${error.statusCode}]: ${error.error.error_summary}`,
);
}
// If that data does not exist for some reason return the actual error
throw error;
} }
} }

View file

@ -9,6 +9,7 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -206,7 +207,7 @@ export class ERPNext implements INodeType {
const properties = this.getNodeParameter('properties', i) as DocumentProperties; const properties = this.getNodeParameter('properties', i) as DocumentProperties;
if (!properties.customProperty.length) { if (!properties.customProperty.length) {
throw new Error('Please enter at least one property for the document to create.'); throw new NodeOperationError(this.getNode(), 'Please enter at least one property for the document to create.');
} }
properties.customProperty.forEach(property => { properties.customProperty.forEach(property => {
@ -242,7 +243,7 @@ export class ERPNext implements INodeType {
const properties = this.getNodeParameter('properties', i) as DocumentProperties; const properties = this.getNodeParameter('properties', i) as DocumentProperties;
if (!properties.customProperty.length) { if (!properties.customProperty.length) {
throw new Error('Please enter at least one property for the document to update.'); throw new NodeOperationError(this.getNode(), 'Please enter at least one property for the document to update.');
} }
properties.customProperty.forEach(property => { properties.customProperty.forEach(property => {

View file

@ -10,7 +10,9 @@ import {
import { import {
IDataObject, IDataObject,
IHookFunctions, IHookFunctions,
IWebhookFunctions IWebhookFunctions,
NodeApiError,
NodeOperationError
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function erpNextApiRequest( export async function erpNextApiRequest(
@ -26,7 +28,7 @@ export async function erpNextApiRequest(
const credentials = this.getCredentials('erpNextApi'); const credentials = this.getCredentials('erpNextApi');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
let options: OptionsWithUri = { let options: OptionsWithUri = {
@ -56,27 +58,15 @@ export async function erpNextApiRequest(
} catch (error) { } catch (error) {
if (error.statusCode === 403) { if (error.statusCode === 403) {
throw new Error( throw new NodeApiError(this.getNode(), { message: `DocType unavailable.` });
`ERPNext error response [${error.statusCode}]: DocType unavailable.`,
);
} }
if (error.statusCode === 307) { if (error.statusCode === 307) {
throw new Error( throw new NodeApiError(this.getNode(), { message:`Please ensure the subdomain is correct.` });
`ERPNext error response [${error.statusCode}]: Please ensure the subdomain is correct.`,
);
} }
let errorMessages; throw new NodeApiError(this.getNode(), error);
if (error?.response?.body?._server_messages) {
const errors = JSON.parse(error.response.body._server_messages);
errorMessages = errors.map((e: string) => JSON.parse(e).message);
throw new Error(
`ARPNext error response [${error.statusCode}]: ${errorMessages.join('|')}`,
);
}
throw error;
} }
} }

View file

@ -10,6 +10,7 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import * as gm from 'gm'; import * as gm from 'gm';
import { file } from 'tmp-promise'; import { file } from 'tmp-promise';
@ -1041,7 +1042,7 @@ export class EditImage implements INodeType {
requiredOperationParameters[operation].forEach(parameterName => { requiredOperationParameters[operation].forEach(parameterName => {
try { try {
operationParameters[parameterName] = this.getNodeParameter(parameterName, itemIndex); operationParameters[parameterName] = this.getNodeParameter(parameterName, itemIndex);
} catch (e) {} } catch (error) {}
}); });
operations = [ operations = [
@ -1055,11 +1056,11 @@ export class EditImage implements INodeType {
if (operations[0].operation !== 'create') { if (operations[0].operation !== 'create') {
// "create" generates a new image so does not require any incoming data. // "create" generates a new image so does not require any incoming data.
if (item.binary === undefined) { if (item.binary === undefined) {
throw new Error('Item does not contain any binary data.'); throw new NodeOperationError(this.getNode(), 'Item does not contain any binary data.');
} }
if (item.binary[dataPropertyName as string] === undefined) { if (item.binary[dataPropertyName as string] === undefined) {
throw new Error(`Item does not contain any binary data with the name "${dataPropertyName}".`); throw new NodeOperationError(this.getNode(), `Item does not contain any binary data with the name "${dataPropertyName}".`);
} }
gmInstance = gm(Buffer.from(item.binary![dataPropertyName as string].data, BINARY_ENCODING)); gmInstance = gm(Buffer.from(item.binary![dataPropertyName as string].data, BINARY_ENCODING));
@ -1095,7 +1096,7 @@ export class EditImage implements INodeType {
const geometryString = (positionX >= 0 ? '+' : '') + positionX + (positionY >= 0 ? '+' : '') + positionY; const geometryString = (positionX >= 0 ? '+' : '') + positionX + (positionY >= 0 ? '+' : '') + positionY;
if (item.binary![operationData.dataPropertyNameComposite as string] === undefined) { if (item.binary![operationData.dataPropertyNameComposite as string] === undefined) {
throw new Error(`Item does not contain any binary data with the name "${operationData.dataPropertyNameComposite}".`); throw new NodeOperationError(this.getNode(), `Item does not contain any binary data with the name "${operationData.dataPropertyNameComposite}".`);
} }
const { fd, path, cleanup } = await file(); const { fd, path, cleanup } = await file();

View file

@ -10,7 +10,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject, NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
interface IContact { interface IContact {
@ -58,20 +58,7 @@ export async function egoiApiRequest(this: IHookFunctions | IExecuteFunctions |
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
let errorMessage; throw new NodeApiError(this.getNode(), error);
if (error.response && error.response.body) {
if (Array.isArray(error.response.body.errors)) {
const errors = error.response.body.errors;
errorMessage = errors.map((e: IDataObject) => e.detail);
} else {
errorMessage = error.response.body.detail;
}
throw new Error(`e-goi Error response [${error.statusCode}]: ${errorMessage}`);
}
throw error;
} }
} }

View file

@ -7,6 +7,7 @@ import {
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
ITriggerResponse, ITriggerResponse,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -168,7 +169,7 @@ export class EmailReadImap implements INodeType {
const credentials = this.getCredentials('imap'); const credentials = this.getCredentials('imap');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const mailbox = this.getNodeParameter('mailbox') as string; const mailbox = this.getNodeParameter('mailbox') as string;
@ -181,8 +182,8 @@ export class EmailReadImap implements INodeType {
if (options.customEmailConfig !== undefined) { if (options.customEmailConfig !== undefined) {
try { try {
searchCriteria = JSON.parse(options.customEmailConfig as string); searchCriteria = JSON.parse(options.customEmailConfig as string);
} catch (err) { } catch (error) {
throw new Error(`Custom email config is not valid JSON.`); throw new NodeOperationError(this.getNode(), `Custom email config is not valid JSON.`);
} }
} }
@ -279,7 +280,7 @@ export class EmailReadImap implements INodeType {
const part = lodash.find(message.parts, { which: '' }); const part = lodash.find(message.parts, { which: '' });
if (part === undefined) { if (part === undefined) {
throw new Error('Email part could not be parsed.'); throw new NodeOperationError(this.getNode(), 'Email part could not be parsed.');
} }
const parsedEmail = await parseRawEmail.call(this, part.body, dataPropertyAttachmentsPrefixName); const parsedEmail = await parseRawEmail.call(this, part.body, dataPropertyAttachmentsPrefixName);
@ -337,7 +338,7 @@ export class EmailReadImap implements INodeType {
const part = lodash.find(message.parts, { which: 'TEXT' }); const part = lodash.find(message.parts, { which: 'TEXT' });
if (part === undefined) { if (part === undefined) {
throw new Error('Email part could not be parsed.'); throw new NodeOperationError(this.getNode(), 'Email part could not be parsed.');
} }
// Return base64 string // Return base64 string
newEmail = { newEmail = {

View file

@ -7,6 +7,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { createTransport } from 'nodemailer'; import { createTransport } from 'nodemailer';
@ -148,7 +149,7 @@ export class EmailSend implements INodeType {
const credentials = this.getCredentials('smtp'); const credentials = this.getCredentials('smtp');
if (credentials === undefined) { if (credentials === undefined) {
throw new Error('No credentials got returned!'); throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
} }
const connectionOptions: SMTPTransport.Options = { const connectionOptions: SMTPTransport.Options = {

View file

@ -6,6 +6,7 @@ import {
import { import {
IHookFunctions, IHookFunctions,
INodePropertyOptions, INodePropertyOptions,
NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
/** /**
@ -18,7 +19,7 @@ export async function emeliaGraphqlRequest(
const response = await emeliaApiRequest.call(this, 'POST', '/graphql', body); const response = await emeliaApiRequest.call(this, 'POST', '/graphql', body);
if (response.errors) { if (response.errors) {
throw new Error(`Emelia error message: ${response.errors[0].message}`); throw new NodeApiError(this.getNode(), response);
} }
return response; return response;
@ -48,19 +49,9 @@ export async function emeliaApiRequest(
}; };
try { try {
return await this.helpers.request!.call(this, options); return await this.helpers.request!.call(this, options);
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error);
if (error?.response?.body?.error) {
const { error: errorMessage } = error.response.body;
throw new Error(
`Emelia error response [${error.statusCode}]: ${errorMessage}`,
);
}
throw error;
} }
} }

Some files were not shown because too many files have changed in this diff Show more