From 1d27a9e87e22f02d3a7762202afd1951b3212a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Fri, 16 Apr 2021 18:33:36 +0200 Subject: [PATCH] :sparkles: Improve node error handling (#1309) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * :construction: create basic setup NodeError * :construction: add httpCodes * :construction: add path priolist * :construction: handle statusCode in error, adjust interfaces * :construction: fixing type issues w/Ivan * :construction: add error exploration * 👔 fix linter issues * :wrench: improve object check * :construction: remove path passing from NodeApiError * :construction: add multi error + refactor findProperty method * 👔 allow any * :wrench: handle multi error message callback * :zap: change return type of callback * :zap: add customCallback to MultiError * :construction: refactor to use INode * :hammer: handle arrays, continue search after first null property found * 🚫 refactor method access * :construction: setup NodeErrorView * :zap: change timestamp to Date.now * :books: Add documentation for methods and constants * :construction: change message setting * 🚚 move NodeErrors to workflow * :sparkles: add new ErrorView for Nodes * :art: improve error notification * :art: refactor interfaces * :zap: add WorkflowOperationError, refactor error throwing * 👕 fix linter issues * :art: rename param * :bug: fix handling normal errors * :zap: add usage of NodeApiError * :art: fix throw new error instead of constructor * :art: remove unnecessary code/comments * :art: adjusted spacing + updated status messages * :art: fix tab indentation * ✨ Replace current errors with custom errors (#1576) * :zap: Introduce NodeApiError in catch blocks * :zap: Introduce NodeOperationError in nodes * :zap: Add missing errors and remove incompatible * :zap: Fix NodeOperationError in incompatible nodes * :wrench: Adjust error handling in missed nodes PayPal, FileMaker, Reddit, Taiga and Facebook Graph API nodes * :hammer: Adjust Strava Trigger node error handling * :hammer: Adjust AWS nodes error handling * :hammer: Remove duplicate instantiation of NodeApiError * :bug: fix strava trigger node error handling * Add XML parsing to NodeApiError constructor (#1633) * :bug: Remove type annotation from catch variable * :sparkles: Add XML parsing to NodeApiError * :zap: Simplify error handling in Rekognition node * :zap: Pass in XML flag in generic functions * :fire: Remove try/catch wrappers at call sites * :hammer: Refactor setting description from XML * :hammer: Refactor let to const in resource loaders * :zap: Find property in parsed XML * :zap: Change let to const * :fire: Remove unneeded try/catch block * :shirt: Fix linting issues * :bug: Fix errors from merge conflict resolution * :zap: Add custom errors to latest contributions * :shirt: Fix linting issues * :zap: Refactor MongoDB helpers for custom errors * :bug: Correct custom error type * :zap: Apply feedback to A nodes * :zap: Apply feedback to missed A node * :zap: Apply feedback to B-D nodes * :zap: Apply feedback to E-F nodes * :zap: Apply feedback to G nodes * :zap: Apply feedback to H-L nodes * :zap: Apply feedback to M nodes * :zap: Apply feedback to P nodes * :zap: Apply feedback to R nodes * :zap: Apply feedback to S nodes * :zap: Apply feedback to T nodes * :zap: Apply feedback to V-Z nodes * :zap: Add HTTP code to iterable node error * :hammer: Standardize e as error * :hammer: Standardize err as error * :zap: Fix error handling for non-standard nodes Co-authored-by: Ben Hesseldieck Co-authored-by: Ben Hesseldieck Co-authored-by: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com> --- packages/cli/commands/execute.ts | 10 +- packages/cli/src/Interfaces.ts | 7 +- packages/cli/src/WebhookHelpers.ts | 7 +- .../cli/src/WorkflowExecuteAdditionalData.ts | 8 +- packages/cli/src/WorkflowRunner.ts | 24 +- packages/cli/src/WorkflowRunnerProcess.ts | 15 +- packages/core/src/Interfaces.ts | 2 - packages/core/src/NodeExecuteFunctions.ts | 11 +- packages/core/src/WorkflowExecute.ts | 14 +- .../src/components/Error/NodeViewError.vue | 177 +++++++++++++ packages/editor-ui/src/components/RunData.vue | 12 +- .../src/components/mixins/pushConnection.ts | 13 +- packages/editor-ui/src/n8n-theme.scss | 4 + .../ActiveCampaign/ActiveCampaign.node.ts | 25 +- .../ActiveCampaignTrigger.node.ts | 2 +- .../nodes/ActiveCampaign/GenericFunctions.ts | 14 +- .../AcuityScheduling/GenericFunctions.ts | 6 +- .../nodes/Affinity/AffinityTrigger.node.ts | 3 +- .../nodes/Affinity/GenericFunctions.ts | 10 +- .../nodes/AgileCrm/AgileCrm.node.ts | 11 +- .../nodes/AgileCrm/GenericFunctions.ts | 8 +- .../nodes/Airtable/Airtable.node.ts | 3 +- .../nodes/Airtable/AirtableTrigger.node.ts | 3 +- .../nodes/Airtable/GenericFunctions.ts | 22 +- packages/nodes-base/nodes/Amqp/Amqp.node.ts | 5 +- .../nodes-base/nodes/Amqp/AmqpTrigger.node.ts | 5 +- .../nodes/ApiTemplateIo/ApiTemplateIo.node.ts | 7 +- .../nodes/ApiTemplateIo/GenericFunctions.ts | 8 +- packages/nodes-base/nodes/Asana/Asana.node.ts | 16 +- .../nodes/Asana/AsanaTrigger.node.ts | 5 +- .../nodes/Asana/GenericFunctions.ts | 24 +- .../nodes/Automizy/GenericFunctions.ts | 11 +- .../nodes/Autopilot/GenericFunctions.ts | 7 +- .../nodes-base/nodes/Aws/AwsLambda.node.ts | 39 ++- packages/nodes-base/nodes/Aws/AwsSns.node.ts | 17 +- .../nodes/Aws/AwsSnsTrigger.node.ts | 11 +- .../nodes/Aws/Comprehend/GenericFunctions.ts | 20 +- .../nodes-base/nodes/Aws/GenericFunctions.ts | 24 +- .../Aws/Rekognition/AwsRekognition.node.ts | 8 +- .../nodes/Aws/Rekognition/GenericFunctions.ts | 22 +- .../nodes-base/nodes/Aws/S3/AwsS3.node.ts | 7 +- .../nodes/Aws/S3/GenericFunctions.ts | 24 +- .../nodes-base/nodes/Aws/SES/AwsSes.node.ts | 5 +- .../nodes/Aws/SES/GenericFunctions.ts | 20 +- .../nodes-base/nodes/Aws/SQS/AwsSqs.node.ts | 14 +- .../nodes/Bannerbear/GenericFunctions.ts | 11 +- .../Beeminder/Beeminder.node.functions.ts | 9 +- .../nodes/Beeminder/Beeminder.node.ts | 3 +- .../nodes/Beeminder/GenericFunctions.ts | 6 +- .../nodes/Bitbucket/BitbucketTrigger.node.ts | 2 +- .../nodes/Bitbucket/GenericFunctions.ts | 8 +- .../nodes/Bitly/GenericFunctions.ts | 14 +- .../nodes/Bitwarden/Bitwarden.node.ts | 7 +- .../nodes/Bitwarden/GenericFunctions.ts | 15 +- packages/nodes-base/nodes/Box/Box.node.ts | 7 +- .../nodes-base/nodes/Box/BoxTrigger.node.ts | 2 +- .../nodes-base/nodes/Box/GenericFunctions.ts | 18 +- .../nodes/Brandfetch/GenericFunctions.ts | 16 +- .../nodes-base/nodes/Bubble/Bubble.node.ts | 3 +- .../nodes/Bubble/GenericFunctions.ts | 7 +- .../nodes/Calendly/CalendlyTrigger.node.ts | 2 +- .../nodes/Calendly/GenericFunctions.ts | 12 +- .../nodes/Chargebee/Chargebee.node.ts | 20 +- .../nodes/CircleCi/GenericFunctions.ts | 15 +- .../nodes/Clearbit/GenericFunctions.ts | 19 +- .../nodes-base/nodes/ClickUp/ClickUp.node.ts | 13 +- .../nodes/ClickUp/GenericFunctions.ts | 19 +- .../nodes/Clockify/GenericFunctions.ts | 17 +- .../nodes/Cockpit/GenericFunctions.ts | 11 +- packages/nodes-base/nodes/Coda/Coda.node.ts | 10 +- .../nodes-base/nodes/Coda/GenericFunctions.ts | 11 +- .../nodes/CoinGecko/GenericFunctions.ts | 5 +- packages/nodes-base/nodes/Compression.node.ts | 9 +- .../nodes/Contentful/GenericFunctions.ts | 6 +- .../nodes/ConvertKit/GenericFunctions.ts | 17 +- .../nodes/Copper/CopperTrigger.node.ts | 2 +- .../nodes/Copper/GenericFunctions.ts | 8 +- .../nodes-base/nodes/Cortex/Cortex.node.ts | 13 +- .../nodes/Cortex/GenericFunctions.ts | 9 +- .../nodes-base/nodes/CrateDb/CrateDb.node.ts | 7 +- .../nodes/CustomerIo/CustomerIo.node.ts | 9 +- .../CustomerIo/CustomerIoTrigger.node.ts | 2 +- .../nodes/CustomerIo/GenericFunctions.ts | 18 +- packages/nodes-base/nodes/DateTime.node.ts | 3 +- .../nodes/DeepL/GenericFunctions.ts | 10 +- .../nodes/Demio/GenericFunctions.ts | 14 +- .../nodes-base/nodes/Discord/Discord.node.ts | 7 +- .../nodes/Discourse/GenericFunctions.ts | 12 +- .../nodes-base/nodes/Disqus/Disqus.node.ts | 5 +- .../nodes/Disqus/GenericFunctions.ts | 20 +- .../nodes/Drift/GenericFunctions.ts | 13 +- .../nodes-base/nodes/Dropbox/Dropbox.node.ts | 7 +- .../nodes/Dropbox/GenericFunctions.ts | 17 +- .../nodes-base/nodes/ERPNext/ERPNext.node.ts | 5 +- .../nodes/ERPNext/GenericFunctions.ts | 24 +- packages/nodes-base/nodes/EditImage.node.ts | 9 +- .../nodes-base/nodes/Egoi/GenericFunctions.ts | 17 +- .../nodes-base/nodes/EmailReadImap.node.ts | 11 +- packages/nodes-base/nodes/EmailSend.node.ts | 3 +- .../nodes/Emelia/GenericFunctions.ts | 15 +- .../Eventbrite/EventbriteTrigger.node.ts | 3 +- .../nodes/Eventbrite/GenericFunctions.ts | 10 +- .../nodes-base/nodes/ExecuteWorkflow.node.ts | 3 +- .../nodes/Facebook/FacebookGraphApi.node.ts | 10 +- .../nodes/Facebook/FacebookTrigger.node.ts | 5 +- .../nodes/Facebook/GenericFunctions.ts | 11 +- .../nodes/FileMaker/FileMaker.node.ts | 38 +-- .../nodes/FileMaker/GenericFunctions.ts | 32 +-- packages/nodes-base/nodes/Flow/Flow.node.ts | 20 +- .../nodes-base/nodes/Flow/FlowTrigger.node.ts | 13 +- .../nodes-base/nodes/Flow/GenericFunctions.ts | 11 +- .../nodes/Freshdesk/Freshdesk.node.ts | 5 +- .../nodes/Freshdesk/GenericFunctions.ts | 14 +- packages/nodes-base/nodes/Ftp.node.ts | 18 +- packages/nodes-base/nodes/Function.node.ts | 15 +- .../nodes-base/nodes/FunctionItem.node.ts | 8 +- .../nodes/GetResponse/GenericFunctions.ts | 8 +- .../GetResponse/GetResponseTrigger.node.ts | 6 +- .../nodes/Ghost/GenericFunctions.ts | 18 +- packages/nodes-base/nodes/Ghost/Ghost.node.ts | 9 +- .../nodes/Github/GenericFunctions.ts | 19 +- .../nodes-base/nodes/Github/Github.node.ts | 7 +- .../nodes/Github/GithubTrigger.node.ts | 22 +- .../nodes/Gitlab/GenericFunctions.ts | 19 +- .../nodes-base/nodes/Gitlab/Gitlab.node.ts | 9 +- .../nodes/Gitlab/GitlabTrigger.node.ts | 16 +- .../nodes/GoToWebinar/GenericFunctions.ts | 15 +- .../nodes/GoToWebinar/GoToWebinar.node.ts | 3 +- .../Google/Analytics/GenericFunctions.ts | 22 +- .../nodes/Google/Books/GenericFunctions.ts | 23 +- .../nodes/Google/Calendar/GenericFunctions.ts | 14 +- .../Google/Calendar/GoogleCalendar.node.ts | 12 +- .../CloudNaturalLanguage/GenericFunctions.ts | 22 +- .../nodes/Google/Contacts/GenericFunctions.ts | 23 +- .../nodes/Google/Drive/GenericFunctions.ts | 26 +- .../nodes/Google/Drive/GoogleDrive.node.ts | 7 +- .../Google/Drive/GoogleDriveTrigger.node.ts | 19 +- .../CloudFirestore/GenericFunctions.ts | 22 +- .../RealtimeDatabase/GenericFunctions.ts | 23 +- .../RealtimeDatabase/RealtimeDatabase.node.ts | 6 +- .../Google/GSuiteAdmin/GSuiteAdmin.node.ts | 5 +- .../Google/GSuiteAdmin/GenericFunctions.ts | 13 +- .../nodes/Google/Gmail/GenericFunctions.ts | 26 +- .../nodes/Google/Sheet/GenericFunctions.ts | 10 +- .../nodes/Google/Sheet/GoogleSheet.ts | 16 +- .../nodes/Google/Sheet/GoogleSheets.node.ts | 3 +- .../nodes/Google/Slides/GenericFunctions.ts | 7 +- .../nodes/Google/Task/GenericFunctions.ts | 14 +- .../Google/Translate/GenericFunctions.ts | 10 +- .../nodes/Google/YouTube/GenericFunctions.ts | 25 +- .../nodes/Google/YouTube/YouTube.node.ts | 11 +- .../nodes/Gotify/GenericFunctions.ts | 12 +- .../nodes-base/nodes/GraphQL/GraphQL.node.ts | 10 +- .../nodes/Gumroad/GenericFunctions.ts | 6 +- .../nodes/HackerNews/GenericFunctions.ts | 10 +- .../nodes/HackerNews/HackerNews.node.ts | 9 +- .../nodes/Harvest/GenericFunctions.ts | 17 +- .../nodes-base/nodes/Harvest/Harvest.node.ts | 23 +- .../nodes/HelpScout/GenericFunctions.ts | 14 +- .../nodes/HelpScout/HelpScout.node.ts | 13 +- .../nodes/HelpScout/HelpScoutTrigger.node.ts | 2 +- .../nodes/HtmlExtract/HtmlExtract.node.ts | 7 +- packages/nodes-base/nodes/HttpRequest.node.ts | 26 +- .../nodes/Hubspot/GenericFunctions.ts | 23 +- .../nodes/Hubspot/HubspotTrigger.node.ts | 10 +- .../nodes/HumanticAI/GenericFunctions.ts | 16 +- .../nodes/HumanticAI/HumanticAi.node.ts | 9 +- .../nodes/Hunter/GenericFunctions.ts | 8 +- packages/nodes-base/nodes/If.node.ts | 7 +- .../nodes/Intercom/GenericFunctions.ts | 11 +- .../nodes/Intercom/Intercom.node.ts | 54 ++-- packages/nodes-base/nodes/Interval.node.ts | 3 +- .../nodes/InvoiceNinja/GenericFunctions.ts | 14 +- .../InvoiceNinja/InvoiceNinjaTrigger.node.ts | 2 +- .../nodes/Iterable/GenericFunctions.ts | 13 +- .../nodes/Iterable/Iterable.node.ts | 14 +- .../nodes-base/nodes/Jira/GenericFunctions.ts | 23 +- packages/nodes-base/nodes/Jira/Jira.node.ts | 13 +- .../nodes-base/nodes/Jira/JiraTrigger.node.ts | 2 +- .../nodes/JotForm/GenericFunctions.ts | 6 +- .../nodes/JotForm/JotFormTrigger.node.ts | 2 +- packages/nodes-base/nodes/Kafka/Kafka.node.ts | 5 +- .../nodes/Kafka/KafkaTrigger.node.ts | 5 +- .../nodes-base/nodes/Keap/GenericFunctions.ts | 8 +- packages/nodes-base/nodes/Keap/Keap.node.ts | 9 +- .../nodes-base/nodes/Keap/KeapTrigger.node.ts | 2 +- .../nodes/Lemlist/GenericFunctions.ts | 8 +- .../nodes-base/nodes/Line/GenericFunctions.ts | 13 +- packages/nodes-base/nodes/Line/Line.node.ts | 5 +- .../nodes/LingvaNex/GenericFunctions.ts | 16 +- .../nodes/LinkedIn/GenericFunctions.ts | 6 +- .../nodes/LinkedIn/LinkedIn.node.ts | 5 +- .../nodes-base/nodes/MQTT/MqttTrigger.node.ts | 7 +- .../nodes/Mailchimp/GenericFunctions.ts | 11 +- .../nodes/Mailchimp/MailchimpTrigger.node.ts | 18 +- .../nodes/MailerLite/GenericFunctions.ts | 13 +- .../MailerLite/MailerLiteTrigger.node.ts | 2 +- .../nodes-base/nodes/Mailgun/Mailgun.node.ts | 12 +- .../nodes/Mailjet/GenericFunctions.ts | 7 +- .../nodes/Mandrill/GenericFunctions.ts | 17 +- .../nodes/Mandrill/Mandrill.node.ts | 6 +- .../nodes/Matrix/GenericFunctions.ts | 21 +- .../nodes-base/nodes/Matrix/Matrix.node.ts | 2 +- .../nodes/Mattermost/GenericFunctions.ts | 19 +- .../nodes/Mattermost/Mattermost.node.ts | 23 +- .../nodes/Mautic/GenericFunctions.ts | 20 +- .../nodes-base/nodes/Mautic/Mautic.node.ts | 11 +- .../nodes/Mautic/MauticTrigger.node.ts | 2 +- .../nodes/Medium/GenericFunctions.ts | 9 +- .../nodes-base/nodes/Medium/Medium.node.ts | 5 +- .../nodes/MessageBird/GenericFunctions.ts | 18 +- .../nodes/MessageBird/MessageBird.node.ts | 5 +- .../nodes/Microsoft/Excel/GenericFunctions.ts | 8 +- .../Microsoft/Excel/MicrosoftExcel.node.ts | 6 +- .../Microsoft/OneDrive/GenericFunctions.ts | 8 +- .../OneDrive/MicrosoftOneDrive.node.ts | 10 +- .../Microsoft/Outlook/GenericFunctions.ts | 7 +- .../Outlook/MicrosoftOutlook.node.ts | 22 +- .../nodes/Microsoft/Sql/MicrosoftSql.node.ts | 9 +- .../nodes/Microsoft/Teams/GenericFunctions.ts | 8 +- .../nodes/Mindee/GenericFunctions.ts | 14 +- .../nodes-base/nodes/Mindee/Mindee.node.ts | 9 +- .../nodes/Mocean/GenericFunctions.ts | 22 +- .../nodes-base/nodes/Mocean/Mocean.node.ts | 5 +- .../nodes/MondayCom/GenericFunctions.ts | 11 +- .../nodes/MondayCom/MondayCom.node.ts | 17 +- .../nodes-base/nodes/MongoDb/MongoDb.node.ts | 6 +- .../nodes/MongoDb/mongo.node.utils.ts | 15 +- .../nodes-base/nodes/MoveBinaryData.node.ts | 3 +- .../nodes/Msg91/GenericFunctions.ts | 22 +- packages/nodes-base/nodes/Msg91/Msg91.node.ts | 5 +- packages/nodes-base/nodes/MySql/MySql.node.ts | 5 +- .../nodes-base/nodes/Nasa/GenericFunctions.ts | 15 +- packages/nodes-base/nodes/Nasa/Nasa.node.ts | 5 +- .../nodes/NextCloud/GenericFunctions.ts | 7 +- .../nodes/NextCloud/NextCloud.node.ts | 9 +- .../nodes/OpenThesaurus/GenericFunctions.ts | 12 +- .../nodes-base/nodes/OpenWeatherMap.node.ts | 16 +- .../nodes/Orbit/GenericFunctions.ts | 14 +- .../nodes-base/nodes/Oura/GenericFunctions.ts | 13 +- .../nodes/Paddle/GenericFunctions.ts | 8 +- .../nodes-base/nodes/Paddle/Paddle.node.ts | 14 +- .../nodes/PagerDuty/GenericFunctions.ts | 12 +- .../nodes/PayPal/GenericFunctions.ts | 23 +- .../nodes-base/nodes/PayPal/PayPal.node.ts | 38 +-- .../nodes/PayPal/PayPalTrigger.node.ts | 22 +- .../nodes/Peekalink/GenericFunctions.ts | 14 +- .../nodes/Phantombuster/GenericFunctions.ts | 16 +- .../nodes/Phantombuster/Phantombuster.node.ts | 5 +- .../nodes/PhilipsHue/GenericFunctions.ts | 13 +- .../nodes/Pipedrive/GenericFunctions.ts | 25 +- .../nodes/Pipedrive/Pipedrive.node.ts | 7 +- .../nodes/Pipedrive/PipedriveTrigger.node.ts | 2 +- .../nodes/Plivo/GenericFunctions.ts | 18 +- .../nodes/PostHog/GenericFunctions.ts | 12 +- .../nodes/Postgres/Postgres.node.ts | 7 +- .../nodes/Postmark/GenericFunctions.ts | 8 +- .../nodes/Postmark/PostmarkTrigger.node.ts | 2 +- .../nodes/ProfitWell/GenericFunctions.ts | 14 +- .../nodes/Pushbullet/GenericFunctions.ts | 13 +- .../nodes/Pushbullet/Pushbullet.node.ts | 5 +- .../nodes/Pushcut/GenericFunctions.ts | 12 +- .../nodes/Pushcut/PushcutTrigger.node.ts | 2 +- .../nodes/Pushover/GenericFunctions.ts | 14 +- .../nodes/Pushover/Pushover.node.ts | 5 +- .../nodes-base/nodes/QuestDb/QuestDb.node.ts | 5 +- .../nodes/QuickBase/GenericFunctions.ts | 20 +- .../nodes/QuickBase/QuickBase.node.ts | 5 +- .../nodes/QuickBooks/GenericFunctions.ts | 14 +- .../nodes/QuickBooks/QuickBooks.node.ts | 35 +-- .../nodes/RabbitMQ/RabbitMQ.node.ts | 8 +- .../nodes/Raindrop/GenericFunctions.ts | 10 +- .../nodes/Raindrop/Raindrop.node.ts | 9 +- .../nodes-base/nodes/ReadBinaryFile.node.ts | 3 +- .../nodes/Reddit/GenericFunctions.ts | 26 +- .../nodes-base/nodes/Reddit/Reddit.node.ts | 5 +- packages/nodes-base/nodes/Redis/Redis.node.ts | 9 +- .../nodes/Rocketchat/GenericFunctions.ts | 13 +- packages/nodes-base/nodes/RssFeedRead.node.ts | 11 +- .../nodes-base/nodes/Rundeck/Rundeck.node.ts | 5 +- .../nodes-base/nodes/Rundeck/RundeckApi.ts | 15 +- .../nodes-base/nodes/S3/GenericFunctions.ts | 24 +- packages/nodes-base/nodes/S3/S3.node.ts | 10 +- .../nodes/Salesforce/GenericFunctions.ts | 7 +- .../nodes/Salesforce/Salesforce.node.ts | 78 +++--- .../nodes/Salesmate/GenericFunctions.ts | 6 +- .../nodes/Salesmate/Salesmate.node.ts | 7 +- .../SecurityScorecard/GenericFunctions.ts | 9 +- .../nodes/Segment/GenericFunctions.ts | 6 +- .../nodes/SendGrid/GenericFunctions.ts | 14 +- .../nodes/SendGrid/SendGrid.node.ts | 5 +- .../nodes/Sendy/GenericFunctions.ts | 14 +- packages/nodes-base/nodes/Sendy/Sendy.node.ts | 14 +- .../nodes/SentryIo/GenericFunctions.ts | 4 +- .../nodes/Shopify/GenericFunctions.ts | 19 +- .../nodes-base/nodes/Shopify/Shopify.node.ts | 3 +- .../nodes/Shopify/ShopifyTrigger.node.ts | 6 +- .../nodes/Signl4/GenericFunctions.ts | 9 +- .../nodes-base/nodes/Signl4/Signl4.node.ts | 5 +- .../nodes/Slack/GenericFunctions.ts | 19 +- packages/nodes-base/nodes/Slack/Slack.node.ts | 13 +- .../nodes/Sms77/GenericFunctions.ts | 6 +- packages/nodes-base/nodes/Sms77/Sms77.node.ts | 6 +- .../nodes/Spontit/GenericFunctions.ts | 13 +- .../nodes/Spotify/GenericFunctions.ts | 30 +-- .../nodes-base/nodes/SpreadsheetFile.node.ts | 7 +- .../nodes/Stackby/GenericFunction.ts | 16 +- .../nodes-base/nodes/Stackby/Stackby.node.ts | 3 +- .../nodes/Storyblok/GenericFunctions.ts | 11 +- .../nodes/Strapi/GenericFunctions.ts | 16 +- .../nodes-base/nodes/Strapi/Strapi.node.ts | 3 +- .../nodes/Strava/GenericFunctions.ts | 21 +- .../nodes/Strava/StravaTrigger.node.ts | 23 +- .../nodes/Stripe/StripeTrigger.node.ts | 16 +- packages/nodes-base/nodes/Stripe/helpers.ts | 16 +- .../nodes/SurveyMonkey/GenericFunctions.ts | 12 +- .../SurveyMonkey/SurveyMonkeyTrigger.node.ts | 8 +- packages/nodes-base/nodes/Switch.node.ts | 13 +- .../nodes/Taiga/GenericFunctions.ts | 13 +- .../nodes/Tapfiliate/GenericFunctions.ts | 20 +- .../nodes/Tapfiliate/Tapfiliate.node.ts | 5 +- .../nodes/Telegram/GenericFunctions.ts | 19 +- .../nodes/Telegram/Telegram.node.ts | 5 +- .../nodes/Telegram/TelegramTrigger.node.ts | 2 +- .../nodes/TheHive/GenericFunctions.ts | 9 +- .../nodes-base/nodes/TheHive/TheHive.node.ts | 19 +- .../nodes/TimescaleDb/TimescaleDb.node.ts | 5 +- .../nodes/Todoist/GenericFunctions.ts | 10 +- .../nodes/Toggl/GenericFunctions.ts | 15 +- .../nodes/Toggl/TogglTrigger.node.ts | 8 +- .../nodes/TravisCi/GenericFunctions.ts | 14 +- .../nodes/Trello/GenericFunctions.ts | 10 +- .../nodes-base/nodes/Trello/Trello.node.ts | 17 +- .../nodes/Trello/TrelloTrigger.node.ts | 11 +- .../nodes/Twake/GenericFunctions.ts | 20 +- packages/nodes-base/nodes/Twake/Twake.node.ts | 3 +- .../nodes/Twilio/GenericFunctions.ts | 22 +- .../nodes-base/nodes/Twilio/Twilio.node.ts | 5 +- .../nodes/Twist/GenericFunctions.ts | 13 +- packages/nodes-base/nodes/Twist/Twist.node.ts | 3 +- .../nodes/Twitter/GenericFunctions.ts | 16 +- .../nodes/Typeform/GenericFunctions.ts | 20 +- .../nodes/Typeform/TypeformTrigger.node.ts | 5 +- .../nodes/UProc/GenericFunctions.ts | 17 +- .../UnleashedSoftware/GenericFunctions.ts | 11 +- .../nodes/Uplead/GenericFunctions.ts | 8 +- .../nodes-base/nodes/Vero/GenericFunctions.ts | 11 +- packages/nodes-base/nodes/Vero/Vero.node.ts | 22 +- .../nodes/Vonage/GenericFunctions.ts | 14 +- .../nodes/Webflow/GenericFunctions.ts | 9 +- .../nodes/Webflow/WebflowTrigger.node.ts | 2 +- packages/nodes-base/nodes/Webhook.node.ts | 6 +- .../nodes/Wekan/GenericFunctions.ts | 10 +- packages/nodes-base/nodes/Wekan/Wekan.node.ts | 11 +- .../nodes-base/nodes/Wise/GenericFunctions.ts | 11 +- .../nodes/WooCommerce/GenericFunctions.ts | 27 +- .../nodes/Wordpress/GenericFunctions.ts | 11 +- .../nodes-base/nodes/WriteBinaryFile.node.ts | 5 +- .../nodes/Wufoo/GenericFunctions.ts | 6 +- .../nodes-base/nodes/Xero/GenericFunctions.ts | 20 +- packages/nodes-base/nodes/Xml.node.ts | 5 +- .../nodes/Yourls/GenericFunctions.ts | 15 +- .../nodes/Zendesk/GenericFunctions.ts | 19 +- .../nodes-base/nodes/Zendesk/Zendesk.node.ts | 10 +- .../nodes/Zendesk/ZendeskTrigger.node.ts | 3 +- .../nodes-base/nodes/Zoho/GenericFunctions.ts | 8 +- .../nodes-base/nodes/Zoom/GenericFunctions.ts | 17 +- .../nodes/Zulip/GenericFunctions.ts | 12 +- packages/nodes-base/nodes/Zulip/Zulip.node.ts | 9 +- packages/workflow/src/Interfaces.ts | 21 +- packages/workflow/src/NodeErrors.ts | 241 ++++++++++++++++++ packages/workflow/src/Workflow.ts | 1 + packages/workflow/src/WorkflowErrors.ts | 16 ++ packages/workflow/src/index.ts | 2 + 374 files changed, 2041 insertions(+), 2826 deletions(-) create mode 100644 packages/editor-ui/src/components/Error/NodeViewError.vue create mode 100644 packages/workflow/src/NodeErrors.ts create mode 100644 packages/workflow/src/WorkflowErrors.ts diff --git a/packages/cli/commands/execute.ts b/packages/cli/commands/execute.ts index 3091778def..864748f8cb 100644 --- a/packages/cli/commands/execute.ts +++ b/packages/cli/commands/execute.ts @@ -167,10 +167,11 @@ export class Execute extends Command { this.log('===================================='); this.log(JSON.stringify(data, null, 2)); - // console.log(data.data.resultData.error); - const error = new Error(data.data.resultData.error.message); - error.stack = data.data.resultData.error.stack; - throw error; + const { error } = data.data.resultData; + throw { + ...error, + stack: error.stack, + }; } this.log('Execution was successfull:'); @@ -182,7 +183,6 @@ export class Execute extends Command { console.error(e.message); console.error(e.stack); this.exit(1); - return; } this.exit(); diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index d5f8f1af3c..ceee653a15 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -1,10 +1,10 @@ import { + ExecutionError, ICredentialDataDecryptedObject, ICredentialsDecrypted, ICredentialsEncrypted, ICredentialType, IDataObject, - IExecutionError, IRun, IRunData, IRunExecutionData, @@ -18,7 +18,6 @@ import { IDeferredPromise, } from 'n8n-core'; - import * as PCancelable from 'p-cancelable'; import { ObjectID, Repository } from 'typeorm'; @@ -374,10 +373,10 @@ export interface ITransferNodeTypes { export interface IWorkflowErrorData { - [key: string]: IDataObject | string | number | IExecutionError; + [key: string]: IDataObject | string | number | ExecutionError; execution: { id?: string; - error: IExecutionError; + error: ExecutionError; lastNodeExecuted: string; mode: WorkflowExecuteMode; }; diff --git a/packages/cli/src/WebhookHelpers.ts b/packages/cli/src/WebhookHelpers.ts index 8c5ad273b6..8913777329 100644 --- a/packages/cli/src/WebhookHelpers.ts +++ b/packages/cli/src/WebhookHelpers.ts @@ -144,7 +144,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] { try { webhookResultData = await workflow.runWebhook(webhookData, workflowStartNode, additionalData, NodeExecuteFunctions, executionMode); - } catch (e) { + } catch (err) { // Send error response to webhook caller const errorMessage = 'Workflow Webhook Error: Workflow could not be started!'; responseCallback(new Error(errorMessage), {}); @@ -156,8 +156,9 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] { runData: {}, lastNodeExecuted: workflowStartNode.name, error: { - message: e.message, - stack: e.stack, + ...err, + message: err.message, + stack: err.stack, }, }, }; diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index 92f6a93afc..970835a90c 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -627,9 +627,11 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi } else { await ActiveExecutions.getInstance().remove(executionId, data); // Workflow did fail - const error = new Error(data.data.resultData.error!.message); - error.stack = data.data.resultData.error!.stack; - throw error; + const { error } = data.data.resultData; + throw { + ...error, + stack: error!.stack, + }; } } diff --git a/packages/cli/src/WorkflowRunner.ts b/packages/cli/src/WorkflowRunner.ts index f884ac5dcc..386f1c51f7 100644 --- a/packages/cli/src/WorkflowRunner.ts +++ b/packages/cli/src/WorkflowRunner.ts @@ -27,12 +27,12 @@ import { } from 'n8n-core'; import { - IDataObject, - IExecutionError, + ExecutionError, IRun, Workflow, WorkflowExecuteMode, WorkflowHooks, + WorkflowOperationError, } from 'n8n-workflow'; import * as config from '../config'; @@ -78,13 +78,13 @@ export class WorkflowRunner { /** * The process did error * - * @param {IExecutionError} error + * @param {ExecutionError} error * @param {Date} startedAt * @param {WorkflowExecuteMode} executionMode * @param {string} executionId * @memberof WorkflowRunner */ - processError(error: IExecutionError, startedAt: Date, executionMode: WorkflowExecuteMode, executionId: string) { + processError(error: ExecutionError, startedAt: Date, executionMode: WorkflowExecuteMode, executionId: string) { const fullRunData: IRun = { data: { resultData: { @@ -250,9 +250,7 @@ export class WorkflowRunner { const fullRunData :IRun = { data: { resultData: { - error: { - message: 'Workflow has been canceled!', - } as IExecutionError, + error: new WorkflowOperationError('Workflow has been canceled!'), runData: {}, }, }, @@ -464,14 +462,14 @@ export class WorkflowRunner { } else if (message.type === 'processError') { clearTimeout(executionTimeout); - const executionError = message.data.executionError as IExecutionError; + const executionError = message.data.executionError as ExecutionError; this.processError(executionError, startedAt, data.executionMode, executionId); } else if (message.type === 'processHook') { this.processHookMessage(workflowHooks, message.data as IProcessMessageDataHook); } else if (message.type === 'timeout') { // 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); } else if (message.type === 'startExecution') { @@ -486,16 +484,12 @@ export class WorkflowRunner { subprocess.on('exit', (code, signal) => { if (signal === 'SIGTERM'){ // 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); } else if (code !== 0) { // Process did exit with error code, so something went wrong. - const executionError = { - message: 'Workflow execution process did crash for an unknown reason!', - } as IExecutionError; + const executionError = new WorkflowOperationError('Workflow execution process did crash for an unknown reason!'); this.processError(executionError, startedAt, data.executionMode, executionId); } diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index 70c140a7e3..b263fa864f 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -16,10 +16,9 @@ import { } from 'n8n-core'; import { + ExecutionError, IDataObject, - IExecuteData, IExecuteWorkflowInfo, - IExecutionError, INodeExecutionData, INodeType, INodeTypeData, @@ -30,6 +29,7 @@ import { IWorkflowExecuteHooks, Workflow, WorkflowHooks, + WorkflowOperationError, } from 'n8n-workflow'; import * as config from '../config'; @@ -270,7 +270,7 @@ process.on('message', async (message: IProcessMessage) => { // Workflow started already executing 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 await workflowRunner.workflowExecute.processSuccessExecution(workflowRunner.startedAt, workflowRunner.workflow!, timeOutError); @@ -301,11 +301,14 @@ process.on('message', async (message: IProcessMessage) => { workflowRunner.executionIdCallback(message.data.executionId); } } catch (error) { + // Catch all uncaught errors and forward them to parent process const executionError = { - message: error.message, - stack: error.stack, - } as IExecutionError; + ...error, + name: error!.name || 'Error', + message: error!.message, + stack: error!.stack, + } as ExecutionError; await sendToParentProcess('processError', { executionError, diff --git a/packages/core/src/Interfaces.ts b/packages/core/src/Interfaces.ts index b67d1ccfc9..763afdf4b8 100644 --- a/packages/core/src/Interfaces.ts +++ b/packages/core/src/Interfaces.ts @@ -18,7 +18,6 @@ import { IWorkflowSettings as IWorkflowSettingsWorkflow, } from 'n8n-workflow'; - import { OptionsWithUri, OptionsWithUrl } from 'request'; import * as requestPromise from 'request-promise-native'; @@ -26,7 +25,6 @@ interface Constructable { new(): T; } - export interface IProcessMessage { data?: any; // tslint:disable-line:no-any type: string; diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 2646fbb7b7..9493e993b8 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -32,6 +32,7 @@ import { IWorkflowExecuteAdditionalData, IWorkflowMetadata, NodeHelpers, + NodeOperationError, NodeParameterValue, Workflow, 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 const nodeType = workflow.nodeTypes.getByName(node.type); 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) { - 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); 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) { @@ -333,10 +334,10 @@ export function getCredentials(workflow: Workflow, node: INode, type: string, ad if (nodeCredentialDescription.required === true) { // Credentials are required so error 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]) { - 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 { // Credentials are not required so resolve with undefined diff --git a/packages/core/src/WorkflowExecute.ts b/packages/core/src/WorkflowExecute.ts index a71bc3bfa3..210711aee7 100644 --- a/packages/core/src/WorkflowExecute.ts +++ b/packages/core/src/WorkflowExecute.ts @@ -1,10 +1,10 @@ import * as PCancelable from 'p-cancelable'; import { + ExecutionError, IConnection, IDataObject, IExecuteData, - IExecutionError, INode, INodeConnections, INodeExecutionData, @@ -17,6 +17,7 @@ import { IWorkflowExecuteAdditionalData, Workflow, WorkflowExecuteMode, + WorkflowOperationError, } from 'n8n-workflow'; import { NodeExecuteFunctions, @@ -490,7 +491,7 @@ export class WorkflowExecute { // Variables which hold temporary data for each node-execution let executionData: IExecuteData; - let executionError: IExecutionError | undefined; + let executionError: ExecutionError | undefined; let executionNode: INode; let nodeSuccessData: INodeExecutionData[][] | null | undefined; let runIndex: number; @@ -517,8 +518,10 @@ export class WorkflowExecute { try { await this.executeHook('workflowExecuteBefore', [workflow]); } catch (error) { + // Set the error that it can be saved correctly executionError = { + ...error, message: error.message, stack: error.stack, }; @@ -683,9 +686,11 @@ export class WorkflowExecute { break; } catch (error) { + this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name; executionError = { + ...error, message: error.message, stack: error.stack, }; @@ -784,7 +789,7 @@ export class WorkflowExecute { })() .then(async () => { 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); }) @@ -792,6 +797,7 @@ export class WorkflowExecute { const fullRunData = this.getFullRunData(startedAt); fullRunData.data.resultData.error = { + ...error, message: error.message, stack: error.stack, }; @@ -815,7 +821,7 @@ export class WorkflowExecute { // @ts-ignore - async processSuccessExecution(startedAt: Date, workflow: Workflow, executionError?: IExecutionError): PCancelable { + async processSuccessExecution(startedAt: Date, workflow: Workflow, executionError?: ExecutionError): PCancelable { const fullRunData = this.getFullRunData(startedAt); if (executionError !== undefined) { diff --git a/packages/editor-ui/src/components/Error/NodeViewError.vue b/packages/editor-ui/src/components/Error/NodeViewError.vue new file mode 100644 index 0000000000..2c8cc2f056 --- /dev/null +++ b/packages/editor-ui/src/components/Error/NodeViewError.vue @@ -0,0 +1,177 @@ + + + + + diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index bc338fa9dc..b64029e69b 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -81,8 +81,7 @@
-
ERROR: {{workflowRunData[node.name][runIndex].error.message}}
-
{{workflowRunData[node.name][runIndex].error.stack}}
+
@@ -226,6 +225,7 @@ import { } from '@/constants'; import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue'; +import NodeErrorView from '@/components/Error/NodeViewError.vue'; import { copyPaste } from '@/components/mixins/copyPaste'; import { genericHelpers } from '@/components/mixins/genericHelpers'; @@ -247,6 +247,7 @@ export default mixins( name: 'RunData', components: { BinaryDataDisplay, + NodeErrorView, VueJsonPretty, }, data () { @@ -739,13 +740,6 @@ export default mixins( } } - .error-display { - .error-message { - color: #ff0000; - font-weight: bold; - } - } - table { border-collapse: collapse; text-align: left; diff --git a/packages/editor-ui/src/components/mixins/pushConnection.ts b/packages/editor-ui/src/components/mixins/pushConnection.ts index 4651301316..5cb47ef44a 100644 --- a/packages/editor-ui/src/components/mixins/pushConnection.ts +++ b/packages/editor-ui/src/components/mixins/pushConnection.ts @@ -207,8 +207,19 @@ export const pushConnection = mixins( if (runDataExecuted.finished !== true) { // There was a problem with executing the workflow let errorMessage = 'There was a problem executing the workflow!'; + if (runDataExecuted.data.resultData.error && runDataExecuted.data.resultData.error.message) { - errorMessage = `There was a problem executing the workflow:
"${runDataExecuted.data.resultData.error.message}"`; + 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:
"${receivedError}"`; } this.$titleSet(workflow.name, 'ERROR'); this.$showMessage({ diff --git a/packages/editor-ui/src/n8n-theme.scss b/packages/editor-ui/src/n8n-theme.scss index 9b252a6c5f..65610ef8c1 100644 --- a/packages/editor-ui/src/n8n-theme.scss +++ b/packages/editor-ui/src/n8n-theme.scss @@ -459,6 +459,10 @@ h1, h2, h3, h4, h5, h6 { border: none; } +.el-notification__content { + text-align: left; +} + // Custom scrollbar ::-webkit-scrollbar { diff --git a/packages/nodes-base/nodes/ActiveCampaign/ActiveCampaign.node.ts b/packages/nodes-base/nodes/ActiveCampaign/ActiveCampaign.node.ts index ecc6dc6e6d..6727c80540 100644 --- a/packages/nodes-base/nodes/ActiveCampaign/ActiveCampaign.node.ts +++ b/packages/nodes-base/nodes/ActiveCampaign/ActiveCampaign.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -431,7 +432,7 @@ export class ActiveCampaign implements INodeType { addAdditionalFields(body.contact as IDataObject, updateFields); } 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') { if (operation === 'create') { @@ -512,7 +513,7 @@ export class ActiveCampaign implements INodeType { addAdditionalFields(body.account as IDataObject, updateFields); } 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') { if (operation === 'create') { @@ -562,7 +563,7 @@ export class ActiveCampaign implements INodeType { endpoint = `/api/3/accountContacts/${accountContactId}`; } 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') { if (operation === 'add') { @@ -592,7 +593,7 @@ export class ActiveCampaign implements INodeType { endpoint = `/api/3/contactTags/${contactTagId}`; } 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') { if (operation === 'add') { @@ -630,7 +631,7 @@ export class ActiveCampaign implements INodeType { dataKey = 'contacts'; } 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') { if (operation === 'getAll') { @@ -732,7 +733,7 @@ export class ActiveCampaign implements INodeType { addAdditionalFields(body.tag as IDataObject, updateFields); } 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') { if (operation === 'create') { @@ -851,7 +852,7 @@ export class ActiveCampaign implements INodeType { endpoint = `/api/3/deals/${dealId}/notes/${dealNoteId}`; } 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') { if (operation === 'create') { @@ -926,7 +927,7 @@ export class ActiveCampaign implements INodeType { endpoint = `/api/3/connections`; } 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') { if (operation === 'create') { @@ -1024,7 +1025,7 @@ export class ActiveCampaign implements INodeType { endpoint = `/api/3/ecomOrders`; } 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') { if (operation === 'create') { @@ -1114,7 +1115,7 @@ export class ActiveCampaign implements INodeType { endpoint = `/api/3/ecomCustomers`; } 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') { if (operation === 'getByProductId') { @@ -1160,11 +1161,11 @@ export class ActiveCampaign implements INodeType { endpoint = `/api/3/ecomOrderProducts`; } else { - throw new Error(`The operation "${operation}" is not known`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`); } } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } let responseData; diff --git a/packages/nodes-base/nodes/ActiveCampaign/ActiveCampaignTrigger.node.ts b/packages/nodes-base/nodes/ActiveCampaign/ActiveCampaignTrigger.node.ts index aad65ee1b4..fb028803d7 100644 --- a/packages/nodes-base/nodes/ActiveCampaign/ActiveCampaignTrigger.node.ts +++ b/packages/nodes-base/nodes/ActiveCampaign/ActiveCampaignTrigger.node.ts @@ -116,7 +116,7 @@ export class ActiveCampaignTrigger implements INodeType { const endpoint = `/api/3/webhooks/${webhookData.webhookId}`; try { await activeCampaignApiRequest.call(this, 'GET', endpoint, {}); - } catch (e) { + } catch (error) { return false; } return true; diff --git a/packages/nodes-base/nodes/ActiveCampaign/GenericFunctions.ts b/packages/nodes-base/nodes/ActiveCampaign/GenericFunctions.ts index 231d2e0d24..215d31934c 100644 --- a/packages/nodes-base/nodes/ActiveCampaign/GenericFunctions.ts +++ b/packages/nodes-base/nodes/ActiveCampaign/GenericFunctions.ts @@ -4,7 +4,7 @@ import { } from 'n8n-core'; import { - IDataObject, ILoadOptionsFunctions, INodeProperties, + IDataObject, ILoadOptionsFunctions, INodeProperties, NodeApiError, NodeOperationError, } from 'n8n-workflow'; 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 { // tslint:disable-line:no-any const credentials = this.getCredentials('activeCampaignApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } if (query === undefined) { @@ -53,7 +53,7 @@ export async function activeCampaignApiRequest(this: IHookFunctions | IExecuteFu const responseData = await this.helpers.request!(options); if (responseData.success === false) { - throw new Error(`ActiveCampaign error response: ${responseData.error} (${responseData.error_info})`); + throw new NodeApiError(this.getNode(), responseData); } if (dataKey === undefined) { @@ -63,13 +63,7 @@ export async function activeCampaignApiRequest(this: IHookFunctions | IExecuteFu } } catch (error) { - if (error.statusCode === 403) { - // 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; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts b/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts index bf13f1aefc..e974fc8042 100644 --- a/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts +++ b/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts @@ -6,7 +6,7 @@ import { ILoadOptionsFunctions, IWebhookFunctions, } 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 { // tslint:disable-line:no-any const authenticationMethod = this.getNodeParameter('authentication', 0); @@ -27,7 +27,7 @@ export async function acuitySchedulingApiRequest(this: IHookFunctions | IExecute if (authenticationMethod === 'apiKey') { const credentials = this.getCredentials('acuitySchedulingApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } options.auth = { @@ -42,6 +42,6 @@ export async function acuitySchedulingApiRequest(this: IHookFunctions | IExecute return await this.helpers.requestOAuth2!.call(this, 'acuitySchedulingOAuth2Api', options, true); } } catch (error) { - throw new Error('Acuity Scheduling Error: ' + error.message); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Affinity/AffinityTrigger.node.ts b/packages/nodes-base/nodes/Affinity/AffinityTrigger.node.ts index 4d3ba70f17..58a608ecca 100644 --- a/packages/nodes-base/nodes/Affinity/AffinityTrigger.node.ts +++ b/packages/nodes-base/nodes/Affinity/AffinityTrigger.node.ts @@ -8,6 +8,7 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeOperationError, } from 'n8n-workflow'; import { @@ -187,7 +188,7 @@ export class AffinityTrigger implements INodeType { const webhookUrl = this.getNodeWebhookUrl('default') as string; 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[]; diff --git a/packages/nodes-base/nodes/Affinity/GenericFunctions.ts b/packages/nodes-base/nodes/Affinity/GenericFunctions.ts index cabc297e65..04443d4d79 100644 --- a/packages/nodes-base/nodes/Affinity/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Affinity/GenericFunctions.ts @@ -12,6 +12,8 @@ import { IDataObject, IHookFunctions, IWebhookFunctions, + NodeApiError, + NodeOperationError, } 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 { // tslint:disable-line:no-any @@ -19,7 +21,7 @@ export async function affinityApiRequest(this: IExecuteFunctions | IWebhookFunct const credentials = this.getCredentials('affinityApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const apiKey = `:${credentials.apiKey}`; @@ -47,11 +49,7 @@ export async function affinityApiRequest(this: IExecuteFunctions | IWebhookFunct try { return await this.helpers.request!(options); } catch (error) { - if (error.response) { - const errorMessage = error.response.body.message || error.response.body.description || error.message; - throw new Error(`Affinity error response [${error.statusCode}]: ${errorMessage}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts b/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts index a79e2c6829..c3401f7d86 100644 --- a/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts +++ b/packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts @@ -3,7 +3,8 @@ import { IDataObject, INodeExecutionData, INodeType, - INodeTypeDescription + INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -149,7 +150,7 @@ export class AgileCrm implements INodeType { Object.assign(body, JSON.parse(additionalFieldsJson)); } 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)); } else { - throw new Error('Additional fields must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON'); } } } else { @@ -483,7 +484,7 @@ export class AgileCrm implements INodeType { if (validateJSON(additionalFieldsJson) !== undefined) { Object.assign(body, JSON.parse(additionalFieldsJson)); } 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)); } else { - throw new Error('Additional fields must be valid JSON'); + throw new NodeOperationError(this.getNode(), 'Additional fields must be valid JSON'); } } diff --git a/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts b/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts index 75b8a71c8b..d0ee821a01 100644 --- a/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts +++ b/packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; import { IContactUpdate } from './ContactInterface'; @@ -39,7 +39,7 @@ export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunction try { return await this.helpers.request!(options); } 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) { if (successfulUpdates.length === 0) { - throw new Error(`AgileCRM error response: ${error.message}`); + throw new NodeApiError(this.getNode(), error); } 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 }); } } diff --git a/packages/nodes-base/nodes/Airtable/Airtable.node.ts b/packages/nodes-base/nodes/Airtable/Airtable.node.ts index 62dc8a47fc..36c2846618 100644 --- a/packages/nodes-base/nodes/Airtable/Airtable.node.ts +++ b/packages/nodes-base/nodes/Airtable/Airtable.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -636,7 +637,7 @@ export class Airtable implements INodeType { } } 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)]; diff --git a/packages/nodes-base/nodes/Airtable/AirtableTrigger.node.ts b/packages/nodes-base/nodes/Airtable/AirtableTrigger.node.ts index abba82b256..0cd4c3e8ad 100644 --- a/packages/nodes-base/nodes/Airtable/AirtableTrigger.node.ts +++ b/packages/nodes-base/nodes/Airtable/AirtableTrigger.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -170,7 +171,7 @@ export class AirtableTrigger implements INodeType { if (Array.isArray(records) && records.length) { 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) { diff --git a/packages/nodes-base/nodes/Airtable/GenericFunctions.ts b/packages/nodes-base/nodes/Airtable/GenericFunctions.ts index 9fbcd5785d..54eb8ea387 100644 --- a/packages/nodes-base/nodes/Airtable/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Airtable/GenericFunctions.ts @@ -13,6 +13,8 @@ import { IDataObject, INodeExecutionData, IPollFunctions, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; @@ -41,7 +43,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa const credentials = this.getCredentials('airtableApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } query = query || {}; @@ -73,23 +75,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa try { return await this.helpers.request!(options); } catch (error) { - if (error.statusCode === 401) { - // 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; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Amqp/Amqp.node.ts b/packages/nodes-base/nodes/Amqp/Amqp.node.ts index 2b4987078a..931b82104f 100644 --- a/packages/nodes-base/nodes/Amqp/Amqp.node.ts +++ b/packages/nodes-base/nodes/Amqp/Amqp.node.ts @@ -11,6 +11,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; export class Amqp implements INodeType { @@ -98,7 +99,7 @@ export class Amqp implements INodeType { async execute(this: IExecuteFunctions): Promise { const credentials = this.getCredentials('amqp'); if (!credentials) { - throw new Error('Credentials are mandatory!'); + throw new NodeOperationError(this.getNode(), 'Credentials are mandatory!'); } const sink = this.getNodeParameter('sink', 0, '') as string; @@ -116,7 +117,7 @@ export class Amqp implements INodeType { } if (sink === '') { - throw new Error('Queue or Topic required!'); + throw new NodeOperationError(this.getNode(), 'Queue or Topic required!'); } const container = create_container(); diff --git a/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts b/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts index be9b91c910..f7f3291923 100644 --- a/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts +++ b/packages/nodes-base/nodes/Amqp/AmqpTrigger.node.ts @@ -12,6 +12,7 @@ import { INodeType, INodeTypeDescription, ITriggerResponse, + NodeOperationError, } from 'n8n-workflow'; @@ -133,7 +134,7 @@ export class AmqpTrigger implements INodeType { const credentials = this.getCredentials('amqp'); if (!credentials) { - throw new Error('Credentials are mandatory!'); + throw new NodeOperationError(this.getNode(), 'Credentials are mandatory!'); } const sink = this.getNodeParameter('sink', '') as string; @@ -146,7 +147,7 @@ export class AmqpTrigger implements INodeType { const containerReconnectLimit = options.reconnectLimit as number || 50; if (sink === '') { - throw new Error('Queue or Topic required!'); + throw new NodeOperationError(this.getNode(), 'Queue or Topic required!'); } let durable = false; diff --git a/packages/nodes-base/nodes/ApiTemplateIo/ApiTemplateIo.node.ts b/packages/nodes-base/nodes/ApiTemplateIo/ApiTemplateIo.node.ts index ef3e325be8..86055ad49c 100644 --- a/packages/nodes-base/nodes/ApiTemplateIo/ApiTemplateIo.node.ts +++ b/packages/nodes-base/nodes/ApiTemplateIo/ApiTemplateIo.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -466,7 +467,7 @@ export class ApiTemplateIo implements INodeType { if (overrideJson !== '') { const data = validateJSON(overrideJson); 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; } @@ -523,14 +524,14 @@ export class ApiTemplateIo implements INodeType { if (jsonParameters === false) { const properties = (this.getNodeParameter('propertiesUi', i) as IDataObject || {}).propertyValues as IDataObject[] || []; 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 }), {}); } else { const propertiesJson = this.getNodeParameter('propertiesJson', i) as string; data = validateJSON(propertiesJson); if (data === undefined) { - throw new Error('A valid JSON must be provided.'); + throw new NodeOperationError(this.getNode(), 'A valid JSON must be provided.'); } } diff --git a/packages/nodes-base/nodes/ApiTemplateIo/GenericFunctions.ts b/packages/nodes-base/nodes/ApiTemplateIo/GenericFunctions.ts index 083683434d..e096d81acf 100644 --- a/packages/nodes-base/nodes/ApiTemplateIo/GenericFunctions.ts +++ b/packages/nodes-base/nodes/ApiTemplateIo/GenericFunctions.ts @@ -6,6 +6,7 @@ import { IExecuteFunctions, ILoadOptionsFunctions, } from 'n8n-core'; +import { NodeApiError } from 'n8n-workflow'; export async function apiTemplateIoApiRequest( this: IExecuteFunctions | ILoadOptionsFunctions, @@ -42,14 +43,11 @@ export async function apiTemplateIoApiRequest( try { const response = await this.helpers.request!(options); if (response.status === 'error') { - throw new Error(response.message); + throw new NodeApiError(this.getNode(), response.message); } return response; } catch (error) { - if (error?.response?.body?.message) { - throw new Error(`APITemplate.io error response [${error.statusCode}]: ${error.response.body.message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Asana/Asana.node.ts b/packages/nodes-base/nodes/Asana/Asana.node.ts index c2ed508654..d7d1971dcf 100644 --- a/packages/nodes-base/nodes/Asana/Asana.node.ts +++ b/packages/nodes-base/nodes/Asana/Asana.node.ts @@ -9,6 +9,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -1639,7 +1641,7 @@ export class Asana implements INodeType { const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {}); 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[] = []; @@ -1674,7 +1676,7 @@ export class Asana implements INodeType { const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {}); 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[] = []; @@ -1711,7 +1713,7 @@ export class Asana implements INodeType { // to retrieve the teams from an organization just work with workspaces that are an organization 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`; @@ -1750,15 +1752,15 @@ export class Asana implements INodeType { let taskData; try { taskData = await asanaApiRequest.call(this, 'GET', `/tasks/${taskId}`, {}); - } catch (e) { - throw new Error(`Could not find task with id "${taskId}" so tags could not be loaded.`); + } catch (error) { + 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 responseData = await asanaApiRequest.call(this, 'GET', endpoint, {}, { workspace }); 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[] = []; @@ -1790,7 +1792,7 @@ export class Asana implements INodeType { const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {}); 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[] = []; diff --git a/packages/nodes-base/nodes/Asana/AsanaTrigger.node.ts b/packages/nodes-base/nodes/Asana/AsanaTrigger.node.ts index 78c00c2fcd..28a57178e8 100644 --- a/packages/nodes-base/nodes/Asana/AsanaTrigger.node.ts +++ b/packages/nodes-base/nodes/Asana/AsanaTrigger.node.ts @@ -10,6 +10,7 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeOperationError, } from 'n8n-workflow'; import { @@ -155,7 +156,7 @@ export class AsanaTrigger implements INodeType { const webhookUrl = this.getNodeWebhookUrl('default') as string; 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; @@ -189,7 +190,7 @@ export class AsanaTrigger implements INodeType { try { await asanaApiRequest.call(this, 'DELETE', endpoint, body); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/Asana/GenericFunctions.ts b/packages/nodes-base/nodes/Asana/GenericFunctions.ts index c480beb078..6741ef96fb 100644 --- a/packages/nodes-base/nodes/Asana/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Asana/GenericFunctions.ts @@ -11,6 +11,8 @@ import { import { IDataObject, INodePropertyOptions, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -43,7 +45,7 @@ export async function asanaApiRequest(this: IHookFunctions | IExecuteFunctions | const credentials = this.getCredentials('asanaApi'); 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}`; @@ -54,25 +56,7 @@ export async function asanaApiRequest(this: IHookFunctions | IExecuteFunctions | return await this.helpers.requestOAuth2.call(this, 'asanaOAuth2Api', options); } } catch (error) { - if (error.statusCode === 401) { - // 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; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Automizy/GenericFunctions.ts b/packages/nodes-base/nodes/Automizy/GenericFunctions.ts index f4265af137..8db073461e 100644 --- a/packages/nodes-base/nodes/Automizy/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Automizy/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function automizyApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise { // tslint:disable-line:no-any @@ -40,14 +40,7 @@ export async function automizyApiRequest(this: IExecuteFunctions | IExecuteSingl //@ts-ignore return await this.helpers.request.call(this, options); } catch (error) { - if (error.response && error.response.body) { - - throw new Error( - `Automizy error response [${error.statusCode}]: ${error.response.body.title}`, - ); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Autopilot/GenericFunctions.ts b/packages/nodes-base/nodes/Autopilot/GenericFunctions.ts index b4477f09b1..4431e433d1 100644 --- a/packages/nodes-base/nodes/Autopilot/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Autopilot/GenericFunctions.ts @@ -11,6 +11,7 @@ import { IDataObject, IHookFunctions, IWebhookFunctions, + NodeApiError, } 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 { // tslint:disable-line:no-any @@ -42,11 +43,7 @@ export async function autopilotApiRequest(this: IExecuteFunctions | IWebhookFunc try { return await this.helpers.request!(options); } catch (error) { - if (error.response) { - const errorMessage = error.response.body.message || error.response.body.description || error.message; - throw new Error(`Autopilot error response [${error.statusCode}]: ${errorMessage}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Aws/AwsLambda.node.ts b/packages/nodes-base/nodes/Aws/AwsLambda.node.ts index 0d08efbaec..75d5e98680 100644 --- a/packages/nodes-base/nodes/Aws/AwsLambda.node.ts +++ b/packages/nodes-base/nodes/Aws/AwsLambda.node.ts @@ -6,6 +6,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { awsApiRequestREST } from './GenericFunctions'; @@ -130,13 +132,7 @@ export class AwsLambda implements INodeType { loadOptions: { async getFunctions(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; - - let data; - try { - data = await awsApiRequestREST.call(this, 'lambda', 'GET', '/2015-03-31/functions/'); - } catch (err) { - throw new Error(`AWS Error: ${err}`); - } + const data = await awsApiRequestREST.call(this, 'lambda', 'GET', '/2015-03-31/functions/'); for (const func of data.Functions!) { returnData.push({ @@ -162,22 +158,17 @@ export class AwsLambda implements INodeType { Qualifier: this.getNodeParameter('qualifier', i) as string, }; - let responseData; - try { - responseData = await awsApiRequestREST.call( - this, - 'lambda', - 'POST', - `/2015-03-31/functions/${params.FunctionName}/invocations?Qualifier=${params.Qualifier}`, - params.Payload, - { - 'X-Amz-Invocation-Type': params.InvocationType, - 'Content-Type': 'application/x-amz-json-1.0', - }, - ); - } catch (err) { - throw new Error(`AWS Error: ${err}`); - } + const responseData = await awsApiRequestREST.call( + this, + 'lambda', + 'POST', + `/2015-03-31/functions/${params.FunctionName}/invocations?Qualifier=${params.Qualifier}`, + params.Payload, + { + 'X-Amz-Invocation-Type': params.InvocationType, + 'Content-Type': 'application/x-amz-json-1.0', + }, + ); if (responseData !== null && responseData.errorMessage !== undefined) { let errorMessage = responseData.errorMessage; @@ -186,7 +177,7 @@ export class AwsLambda implements INodeType { errorMessage += `\n\nStack trace:\n${responseData.stackTrace}`; } - throw new Error(errorMessage); + throw new NodeApiError(this.getNode(), responseData); } else { returnData.push({ result: responseData, diff --git a/packages/nodes-base/nodes/Aws/AwsSns.node.ts b/packages/nodes-base/nodes/Aws/AwsSns.node.ts index 16eb1c4328..9d25a6762a 100644 --- a/packages/nodes-base/nodes/Aws/AwsSns.node.ts +++ b/packages/nodes-base/nodes/Aws/AwsSns.node.ts @@ -6,6 +6,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { awsApiRequestSOAP } from './GenericFunctions'; @@ -107,12 +109,7 @@ export class AwsSns implements INodeType { // select them easily async getTopics(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; - let data; - try { - data = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=ListTopics'); - } catch (err) { - throw new Error(`AWS Error: ${err}`); - } + const data = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=ListTopics'); let topics = data.ListTopicsResponse.ListTopicsResult.Topics.member; @@ -149,12 +146,8 @@ export class AwsSns implements INodeType { 'Message=' + this.getNodeParameter('message', i) as string, ]; - let responseData; - try { - responseData = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=Publish&' + params.join('&')); - } catch (err) { - throw new Error(`AWS Error: ${err}`); - } + + const responseData = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=Publish&' + params.join('&')); returnData.push({MessageId: responseData.PublishResponse.PublishResult.MessageId} as IDataObject); } diff --git a/packages/nodes-base/nodes/Aws/AwsSnsTrigger.node.ts b/packages/nodes-base/nodes/Aws/AwsSnsTrigger.node.ts index e41f9de57f..79197b1104 100644 --- a/packages/nodes-base/nodes/Aws/AwsSnsTrigger.node.ts +++ b/packages/nodes-base/nodes/Aws/AwsSnsTrigger.node.ts @@ -9,6 +9,8 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -68,12 +70,7 @@ export class AwsSnsTrigger implements INodeType { // select them easily async getTopics(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; - let data; - try { - data = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=ListTopics'); - } catch (err) { - throw new Error(`AWS Error: ${err}`); - } + const data = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=ListTopics'); let topics = data.ListTopicsResponse.ListTopicsResult.Topics.member; @@ -134,7 +131,7 @@ export class AwsSnsTrigger implements INodeType { const topic = this.getNodeParameter('topic') as string; 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 = [ diff --git a/packages/nodes-base/nodes/Aws/Comprehend/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/Comprehend/GenericFunctions.ts index 9e6731ebbb..13c389e26a 100644 --- a/packages/nodes-base/nodes/Aws/Comprehend/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/Comprehend/GenericFunctions.ts @@ -22,7 +22,7 @@ import { } from 'n8n-core'; import { - ICredentialDataDecryptedObject, + ICredentialDataDecryptedObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; 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 { // tslint:disable-line:no-any const credentials = this.getCredentials('aws'); 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 @@ -61,17 +61,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I try { return await this.helpers.request!(options); } catch (error) { - const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message; - - 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}`); + throw new NodeApiError(this.getNode(), error); // no XML parsing needed } } @@ -79,7 +69,7 @@ export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions const response = await awsApiRequest.call(this, service, method, path, body, headers); try { return JSON.parse(response); - } catch (e) { + } catch (error) { return response; } } @@ -95,7 +85,7 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions resolve(data); }); }); - } catch (e) { + } catch (error) { return response; } } diff --git a/packages/nodes-base/nodes/Aws/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/GenericFunctions.ts index edc733af64..59caff7b82 100644 --- a/packages/nodes-base/nodes/Aws/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/GenericFunctions.ts @@ -1,7 +1,7 @@ import { URL } from 'url'; import { sign } from 'aws4'; import { OptionsWithUri } from 'request'; -import { parseString } from 'xml2js'; +import { parseString as parseXml } from 'xml2js'; import { IExecuteFunctions, @@ -11,7 +11,7 @@ import { } from 'n8n-core'; import { - ICredentialDataDecryptedObject, + ICredentialDataDecryptedObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; 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 { // tslint:disable-line:no-any const credentials = this.getCredentials('aws'); 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 @@ -52,17 +52,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I try { return await this.helpers.request!(options); } catch (error) { - const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message; - - 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}`); + throw new NodeApiError(this.getNode(), error, { parseXml: true }); } } @@ -70,7 +60,7 @@ export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions const response = await awsApiRequest.call(this, service, method, path, body, headers); try { return JSON.parse(response); - } catch (e) { + } catch (error) { return response; } } @@ -79,14 +69,14 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions const response = await awsApiRequest.call(this, service, method, path, body, headers); try { return await new Promise((resolve, reject) => { - parseString(response, { explicitArray: false }, (err, data) => { + parseXml(response, { explicitArray: false }, (err, data) => { if (err) { return reject(err); } resolve(data); }); }); - } catch (e) { + } catch (error) { return response; } } diff --git a/packages/nodes-base/nodes/Aws/Rekognition/AwsRekognition.node.ts b/packages/nodes-base/nodes/Aws/Rekognition/AwsRekognition.node.ts index ad6618232c..afc5dfe875 100644 --- a/packages/nodes-base/nodes/Aws/Rekognition/AwsRekognition.node.ts +++ b/packages/nodes-base/nodes/Aws/Rekognition/AwsRekognition.node.ts @@ -8,6 +8,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -459,11 +461,11 @@ export class AwsRekognition implements INodeType { const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; 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) { - 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]; @@ -494,7 +496,9 @@ export class AwsRekognition implements INodeType { 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' }); + } } } diff --git a/packages/nodes-base/nodes/Aws/Rekognition/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/Rekognition/GenericFunctions.ts index 8afb205f9c..38a41a3235 100644 --- a/packages/nodes-base/nodes/Aws/Rekognition/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/Rekognition/GenericFunctions.ts @@ -27,6 +27,8 @@ import { import { IDataObject, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; 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 { // tslint:disable-line:no-any const credentials = this.getCredentials('aws'); 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); @@ -59,17 +61,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I try { return await this.helpers.request!(options); } catch (error) { - const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message; - - 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}`); + throw new NodeApiError(this.getNode(), error); } } @@ -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); try { return JSON.parse(response); - } catch (e) { + } catch (error) { return response; } } @@ -93,8 +85,8 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions resolve(data); }); }); - } catch (e) { - return e; + } catch (error) { + return error; } } diff --git a/packages/nodes-base/nodes/Aws/S3/AwsS3.node.ts b/packages/nodes-base/nodes/Aws/S3/AwsS3.node.ts index c6d714a898..08a2a531a4 100644 --- a/packages/nodes-base/nodes/Aws/S3/AwsS3.node.ts +++ b/packages/nodes-base/nodes/Aws/S3/AwsS3.node.ts @@ -23,6 +23,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -422,7 +423,7 @@ export class AwsS3 implements INodeType { const fileName = fileKey.split('/')[fileKey.split('/').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: '' }); @@ -588,11 +589,11 @@ export class AwsS3 implements INodeType { const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; 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) { - 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]; diff --git a/packages/nodes-base/nodes/Aws/S3/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/S3/GenericFunctions.ts index 0c5e3b83aa..e422b449cf 100644 --- a/packages/nodes-base/nodes/Aws/S3/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/S3/GenericFunctions.ts @@ -26,20 +26,20 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } 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 { // tslint:disable-line:no-any const credentials = this.getCredentials('aws'); 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); // Sign AWS API request with the user credentials const signOpts = {headers: headers || {}, host: endpoint.host, method, path: `${endpoint.pathname}?${queryToString(query).replace(/\+/g, '%2B')}`, body}; - + sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim()}); @@ -57,17 +57,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I try { return await this.helpers.request!(options); } catch (error) { - const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message; - - 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}`); + throw new NodeApiError(this.getNode(), error, { parseXml: true }); } } @@ -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); try { return JSON.parse(response); - } catch (e) { + } catch (error) { return response; } } @@ -91,8 +81,8 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions resolve(data); }); }); - } catch (e) { - return e; + } catch (error) { + return error; } } diff --git a/packages/nodes-base/nodes/Aws/SES/AwsSes.node.ts b/packages/nodes-base/nodes/Aws/SES/AwsSes.node.ts index 58d77eb18c..251b9b0e8e 100644 --- a/packages/nodes-base/nodes/Aws/SES/AwsSes.node.ts +++ b/packages/nodes-base/nodes/Aws/SES/AwsSes.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -1098,7 +1099,7 @@ export class AwsSes implements INodeType { if (toAddresses.length) { setParameter(params, 'Destination.ToAddresses.member', toAddresses); } 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) { @@ -1151,7 +1152,7 @@ export class AwsSes implements INodeType { if (toAddresses.length) { setParameter(params, 'Destination.ToAddresses.member', toAddresses); } 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) { diff --git a/packages/nodes-base/nodes/Aws/SES/GenericFunctions.ts b/packages/nodes-base/nodes/Aws/SES/GenericFunctions.ts index 915a5805a6..46b33d6a09 100644 --- a/packages/nodes-base/nodes/Aws/SES/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Aws/SES/GenericFunctions.ts @@ -22,7 +22,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; 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 { // tslint:disable-line:no-any const credentials = this.getCredentials('aws'); 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); @@ -52,17 +52,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I try { return await this.helpers.request!(options); } catch (error) { - const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message; - - 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}`); + throw new NodeApiError(this.getNode(), error, { parseXml: true }); } } @@ -70,7 +60,7 @@ export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions const response = await awsApiRequest.call(this, service, method, path, body, headers); try { return JSON.parse(response); - } catch (e) { + } catch (error) { return response; } } @@ -86,7 +76,7 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions resolve(data); }); }); - } catch (e) { + } catch (error) { return response; } } diff --git a/packages/nodes-base/nodes/Aws/SQS/AwsSqs.node.ts b/packages/nodes-base/nodes/Aws/SQS/AwsSqs.node.ts index fb58bd7139..63a5dd4ff9 100644 --- a/packages/nodes-base/nodes/Aws/SQS/AwsSqs.node.ts +++ b/packages/nodes-base/nodes/Aws/SQS/AwsSqs.node.ts @@ -11,6 +11,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -270,8 +272,8 @@ export class AwsSqs implements INodeType { try { // loads first 1000 queues from SQS data = await awsApiRequestSOAP.call(this, 'sqs', 'GET', `?Action=ListQueues`); - } catch (err) { - throw new Error(`AWS Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } let queues = data.ListQueuesResponse.ListQueuesResult.QueueUrl; @@ -349,11 +351,11 @@ export class AwsSqs implements INodeType { const item = items[i]; 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) { - 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; @@ -374,8 +376,8 @@ export class AwsSqs implements INodeType { let responseData; try { responseData = await awsApiRequestSOAP.call(this, 'sqs', 'GET', `${queuePath}/?Action=${operation}&` + params.join('&')); - } catch (err) { - throw new Error(`AWS Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } const result = responseData.SendMessageResponse.SendMessageResult; diff --git a/packages/nodes-base/nodes/Bannerbear/GenericFunctions.ts b/packages/nodes-base/nodes/Bannerbear/GenericFunctions.ts index 396c7e52ec..45d7996ead 100644 --- a/packages/nodes-base/nodes/Bannerbear/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Bannerbear/GenericFunctions.ts @@ -11,6 +11,8 @@ import { IDataObject, IHookFunctions, IWebhookFunctions, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -22,7 +24,7 @@ export async function bannerbearApiRequest(this: IExecuteFunctions | IWebhookFun const credentials = this.getCredentials('bannerbearApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const options: OptionsWithUri = { @@ -46,12 +48,7 @@ export async function bannerbearApiRequest(this: IExecuteFunctions | IWebhookFun try { return await this.helpers.request!(options); } catch (error) { - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - //@ts-ignore - throw new Error(`Bannerbear error response [${error.statusCode}]: ${error.response.body.message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Beeminder/Beeminder.node.functions.ts b/packages/nodes-base/nodes/Beeminder/Beeminder.node.functions.ts index 4f332faf47..17cf295342 100644 --- a/packages/nodes-base/nodes/Beeminder/Beeminder.node.functions.ts +++ b/packages/nodes-base/nodes/Beeminder/Beeminder.node.functions.ts @@ -7,6 +7,7 @@ import { IDataObject, IHookFunctions, IWebhookFunctions, + NodeOperationError, } from 'n8n-workflow'; import { @@ -18,7 +19,7 @@ export async function createDatapoint(this: IExecuteFunctions | IWebhookFunction const credentials = this.getCredentials('beeminderApi'); 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`; @@ -30,7 +31,7 @@ export async function getAllDatapoints(this: IExecuteFunctions | IHookFunctions const credentials = this.getCredentials('beeminderApi'); 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`; @@ -46,7 +47,7 @@ export async function updateDatapoint(this: IExecuteFunctions | IWebhookFunction const credentials = this.getCredentials('beeminderApi'); 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`; @@ -58,7 +59,7 @@ export async function deleteDatapoint(this: IExecuteFunctions | IWebhookFunction const credentials = this.getCredentials('beeminderApi'); 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`; diff --git a/packages/nodes-base/nodes/Beeminder/Beeminder.node.ts b/packages/nodes-base/nodes/Beeminder/Beeminder.node.ts index 966c783e25..435e1ecb30 100644 --- a/packages/nodes-base/nodes/Beeminder/Beeminder.node.ts +++ b/packages/nodes-base/nodes/Beeminder/Beeminder.node.ts @@ -10,6 +10,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -308,7 +309,7 @@ export class Beeminder implements INodeType { const credentials = this.getCredentials('beeminderApi'); 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`; diff --git a/packages/nodes-base/nodes/Beeminder/GenericFunctions.ts b/packages/nodes-base/nodes/Beeminder/GenericFunctions.ts index d991e57a03..722646f40b 100644 --- a/packages/nodes-base/nodes/Beeminder/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Beeminder/GenericFunctions.ts @@ -11,6 +11,7 @@ import { IDataObject, IHookFunctions, IWebhookFunctions, + NodeApiError, } from 'n8n-workflow'; const BEEMINDER_URI = 'https://www.beeminder.com/api/v1'; @@ -40,10 +41,7 @@ export async function beeminderApiRequest(this: IExecuteFunctions | IWebhookFunc try { return await this.helpers.request!(options); } catch (error) { - if (error?.message) { - throw new Error(`Beeminder error response [${error.statusCode}]: ${error.message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Bitbucket/BitbucketTrigger.node.ts b/packages/nodes-base/nodes/Bitbucket/BitbucketTrigger.node.ts index 6fa13fad2f..005dc15dae 100644 --- a/packages/nodes-base/nodes/Bitbucket/BitbucketTrigger.node.ts +++ b/packages/nodes-base/nodes/Bitbucket/BitbucketTrigger.node.ts @@ -280,7 +280,7 @@ export class BitbucketTrigger implements INodeType { } try { await bitbucketApiRequest.call(this, 'GET', endpoint); - } catch (e) { + } catch (error) { return false; } return true; diff --git a/packages/nodes-base/nodes/Bitbucket/GenericFunctions.ts b/packages/nodes-base/nodes/Bitbucket/GenericFunctions.ts index 4963f2c10c..3c2f88ab23 100644 --- a/packages/nodes-base/nodes/Bitbucket/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Bitbucket/GenericFunctions.ts @@ -5,12 +5,12 @@ import { IHookFunctions, ILoadOptionsFunctions, } 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 { // tslint:disable-line:no-any const credentials = this.getCredentials('bitbucketApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { method, @@ -30,8 +30,8 @@ export async function bitbucketApiRequest(this: IHookFunctions | IExecuteFunctio try { return await this.helpers.request!(options); - } catch (err) { - throw new Error('Bitbucket Error: ' + err.message); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Bitly/GenericFunctions.ts b/packages/nodes-base/nodes/Bitly/GenericFunctions.ts index b12da33938..3875d10182 100644 --- a/packages/nodes-base/nodes/Bitly/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Bitly/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } 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 { // tslint:disable-line:no-any @@ -32,7 +32,7 @@ export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions | if (authenticationMethod === 'accessToken') { const credentials = this.getCredentials('bitlyApi'); 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}`}; @@ -42,15 +42,7 @@ export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions | return await this.helpers.requestOAuth2!.call(this, 'bitlyOAuth2Api', options, { tokenType: 'Bearer' }); } } catch(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; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Bitwarden/Bitwarden.node.ts b/packages/nodes-base/nodes/Bitwarden/Bitwarden.node.ts index 4904329013..1211ae5733 100644 --- a/packages/nodes-base/nodes/Bitwarden/Bitwarden.node.ts +++ b/packages/nodes-base/nodes/Bitwarden/Bitwarden.node.ts @@ -8,6 +8,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -177,7 +178,7 @@ export class Bitwarden implements INodeType { const updateFields = this.getNodeParameter('updateFields', i) as CollectionUpdateFields; 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; @@ -308,7 +309,7 @@ export class Bitwarden implements INodeType { const updateFields = this.getNodeParameter('updateFields', i) as GroupUpdateFields; 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 @@ -452,7 +453,7 @@ export class Bitwarden implements INodeType { const updateFields = this.getNodeParameter('updateFields', i) as MemberUpdateFields; 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; diff --git a/packages/nodes-base/nodes/Bitwarden/GenericFunctions.ts b/packages/nodes-base/nodes/Bitwarden/GenericFunctions.ts index a46df9d46f..48e739268c 100644 --- a/packages/nodes-base/nodes/Bitwarden/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Bitwarden/GenericFunctions.ts @@ -6,6 +6,7 @@ import { IDataObject, ILoadOptionsFunctions, INodePropertyOptions, + NodeApiError, } from 'n8n-workflow'; import { @@ -48,17 +49,7 @@ export async function bitwardenApiRequest( try { return await this.helpers.request!(options); } catch (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; + throw new NodeApiError(this.getNode(), error); } } @@ -93,7 +84,7 @@ export async function getAccessToken( const { access_token } = await this.helpers.request!(options); return access_token; } catch (error) { - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Box/Box.node.ts b/packages/nodes-base/nodes/Box/Box.node.ts index 8a835451b0..75006e46c2 100644 --- a/packages/nodes-base/nodes/Box/Box.node.ts +++ b/packages/nodes-base/nodes/Box/Box.node.ts @@ -9,6 +9,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -217,11 +218,11 @@ export class Box implements INodeType { const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; 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 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]; @@ -248,7 +249,7 @@ export class Box implements INodeType { const content = this.getNodeParameter('fileContent', i) as string; if (fileName === '') { - throw new Error('File name must be set!'); + throw new NodeOperationError(this.getNode(), 'File name must be set!'); } attributes['name'] = fileName; diff --git a/packages/nodes-base/nodes/Box/BoxTrigger.node.ts b/packages/nodes-base/nodes/Box/BoxTrigger.node.ts index 03cb4a75dc..534f11a993 100644 --- a/packages/nodes-base/nodes/Box/BoxTrigger.node.ts +++ b/packages/nodes-base/nodes/Box/BoxTrigger.node.ts @@ -327,7 +327,7 @@ export class BoxTrigger implements INodeType { try { await boxApiRequest.call(this, 'DELETE', endpoint); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/Box/GenericFunctions.ts b/packages/nodes-base/nodes/Box/GenericFunctions.ts index 1a586c1df5..1483b7f43d 100644 --- a/packages/nodes-base/nodes/Box/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Box/GenericFunctions.ts @@ -12,6 +12,7 @@ import { import { IDataObject, IOAuth2Options, + NodeApiError, } 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 { // 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); } catch (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; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Brandfetch/GenericFunctions.ts b/packages/nodes-base/nodes/Brandfetch/GenericFunctions.ts index f3ae6e48ca..83c404e869 100644 --- a/packages/nodes-base/nodes/Brandfetch/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Brandfetch/GenericFunctions.ts @@ -10,14 +10,14 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } 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 { // tslint:disable-line:no-any try { const credentials = this.getCredentials('brandfetchApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { headers: { @@ -46,20 +46,12 @@ export async function brandfetchApiRequest(this: IHookFunctions | IExecuteFuncti const response = await this.helpers.request!(options); if (response.statusCode && response.statusCode !== 200) { - throw new Error(`Brandfetch error response [${response.statusCode}]: ${response.response}`); + throw new NodeApiError(this.getNode(), response); } return response; } catch (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; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Bubble/Bubble.node.ts b/packages/nodes-base/nodes/Bubble/Bubble.node.ts index 433657bb82..541a90368a 100644 --- a/packages/nodes-base/nodes/Bubble/Bubble.node.ts +++ b/packages/nodes-base/nodes/Bubble/Bubble.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -153,7 +154,7 @@ export class Bubble implements INodeType { const filter = options.filtersJson as string; const data = validateJSON(filter); 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); } diff --git a/packages/nodes-base/nodes/Bubble/GenericFunctions.ts b/packages/nodes-base/nodes/Bubble/GenericFunctions.ts index 72cedfb8b7..981b9e10c8 100644 --- a/packages/nodes-base/nodes/Bubble/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Bubble/GenericFunctions.ts @@ -6,6 +6,7 @@ import { import { IDataObject, ILoadOptionsFunctions, + NodeApiError, } from 'n8n-workflow'; import { @@ -57,11 +58,7 @@ export async function bubbleApiRequest( try { return await this.helpers.request!(options); } catch (error) { - if (error?.response?.body?.body?.message) { - const errorMessage = error.response.body.body.message; - throw new Error(`Bubble.io error response [${error.statusCode}]: ${errorMessage}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts b/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts index 8369ab3ebf..62589964de 100644 --- a/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts +++ b/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts @@ -122,7 +122,7 @@ export class CalendlyTrigger implements INodeType { try { await calendlyApiRequest.call(this, 'DELETE', endpoint); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/Calendly/GenericFunctions.ts b/packages/nodes-base/nodes/Calendly/GenericFunctions.ts index 464d8a5bfb..d2b1e6acf5 100644 --- a/packages/nodes-base/nodes/Calendly/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Calendly/GenericFunctions.ts @@ -8,7 +8,9 @@ import { import { IDataObject, IHookFunctions, - IWebhookFunctions + IWebhookFunctions, + NodeApiError, + NodeOperationError, } 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 { // tslint:disable-line:no-any @@ -16,7 +18,7 @@ export async function calendlyApiRequest(this: IExecuteFunctions | IWebhookFunct const credentials = this.getCredentials('calendlyApi'); 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'; @@ -42,10 +44,6 @@ export async function calendlyApiRequest(this: IExecuteFunctions | IWebhookFunct try { return await this.helpers.request!(options); } catch (error) { - if (error.response) { - const errorMessage = error.response.body.message || error.response.body.description || error.message; - throw new Error(`Calendly error response [${error.statusCode}]: ${errorMessage}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Chargebee/Chargebee.node.ts b/packages/nodes-base/nodes/Chargebee/Chargebee.node.ts index 1970910c35..ccbb19b65b 100644 --- a/packages/nodes-base/nodes/Chargebee/Chargebee.node.ts +++ b/packages/nodes-base/nodes/Chargebee/Chargebee.node.ts @@ -4,6 +4,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, NodeParameterValue, } from 'n8n-workflow'; @@ -489,7 +491,7 @@ export class Chargebee implements INodeType { const credentials = this.getCredentials('chargebeeApi'); 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`; @@ -531,7 +533,7 @@ export class Chargebee implements INodeType { endpoint = `customers`; } 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') { @@ -569,7 +571,7 @@ export class Chargebee implements INodeType { const invoiceId = this.getNodeParameter('invoiceId', i) as string; endpoint = `invoices/${invoiceId.trim()}/pdf`; } 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') { @@ -595,10 +597,10 @@ export class Chargebee implements INodeType { endpoint = `subscriptions/${subscriptionId.trim()}/delete`; } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } const options = { @@ -613,7 +615,13 @@ export class Chargebee implements INodeType { 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') { responseData.list.forEach((data: IDataObject) => { diff --git a/packages/nodes-base/nodes/CircleCi/GenericFunctions.ts b/packages/nodes-base/nodes/CircleCi/GenericFunctions.ts index 9548586fde..3e40012303 100644 --- a/packages/nodes-base/nodes/CircleCi/GenericFunctions.ts +++ b/packages/nodes-base/nodes/CircleCi/GenericFunctions.ts @@ -10,13 +10,13 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } 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 { // tslint:disable-line:no-any const credentials = this.getCredentials('circleCiApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { headers: { @@ -35,14 +35,9 @@ export async function circleciApiRequest(this: IHookFunctions | IExecuteFunction } try { return await this.helpers.request!(options); - } catch (err) { - if (err.response && err.response.body && err.response.body.message) { - // 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; } + } catch (error) { + throw new NodeApiError(this.getNode(), error); + } } /** diff --git a/packages/nodes-base/nodes/Clearbit/GenericFunctions.ts b/packages/nodes-base/nodes/Clearbit/GenericFunctions.ts index e22d0e607e..1dc18b0482 100644 --- a/packages/nodes-base/nodes/Clearbit/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Clearbit/GenericFunctions.ts @@ -9,14 +9,12 @@ import { ILoadOptionsFunctions, } from 'n8n-core'; -import { - IDataObject, -} from 'n8n-workflow'; +import { IDataObject, NodeApiError, NodeOperationError, } 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 { // tslint:disable-line:no-any const credentials = this.getCredentials('clearbitApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { headers: { Authorization: `Bearer ${credentials.apiKey}` }, @@ -33,17 +31,6 @@ export async function clearbitApiRequest(this: IHookFunctions | IExecuteFunction try { return await this.helpers.request!(options); } catch (error) { - if (error.statusCode === 401) { - // 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); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts b/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts index 14628f6c5d..09d4dfc97b 100644 --- a/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts +++ b/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -719,14 +720,14 @@ export class ClickUp implements INodeType { }; if (type === 'number' || type === 'currency') { 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' || type === 'automatic' || type === 'currency') { if (additionalFields.stepsStart === 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) { @@ -845,7 +846,7 @@ export class ClickUp implements INodeType { if (additionalFields.customFieldsJson) { const customFields = validateJSON(additionalFields.customFieldsJson as string); if (customFields === undefined) { - throw new Error('Custom Fields: Invalid JSON'); + throw new NodeOperationError(this.getNode(), 'Custom Fields: Invalid JSON'); } body.custom_fields = customFields; } @@ -1042,7 +1043,7 @@ export class ClickUp implements INodeType { if (jsonParse === true) { body.value = validateJSON(body.value); if (body.value === undefined) { - throw new Error('Value is invalid JSON!'); + throw new NodeOperationError(this.getNode(), 'Value is invalid JSON!'); } } else { //@ts-ignore @@ -1213,7 +1214,7 @@ export class ClickUp implements INodeType { if (responseData.data) { responseData = responseData.data; } 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') { @@ -1233,7 +1234,7 @@ export class ClickUp implements INodeType { if (tagsUi) { const tags = (tagsUi as IDataObject).tagsValues as IDataObject[]; 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; } diff --git a/packages/nodes-base/nodes/ClickUp/GenericFunctions.ts b/packages/nodes-base/nodes/ClickUp/GenericFunctions.ts index 0de73a430b..7213056fe8 100644 --- a/packages/nodes-base/nodes/ClickUp/GenericFunctions.ts +++ b/packages/nodes-base/nodes/ClickUp/GenericFunctions.ts @@ -13,8 +13,10 @@ import { import { IDataObject, IOAuth2Options, + NodeApiError, } 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 { // tslint:disable-line:no-any const options: OptionsWithUri = { headers: { @@ -34,15 +36,10 @@ export async function clickupApiRequest(this: IHookFunctions | IExecuteFunctions const credentials = this.getCredentials('clickUpApi'); - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - - options.headers!['Authorization'] = credentials.accessToken; + options.headers!['Authorization'] = credentials?.accessToken; return await this.helpers.request!(options); } else { - const oAuth2Options: IOAuth2Options = { keepBearer: false, tokenType: 'Bearer', @@ -50,15 +47,9 @@ export async function clickupApiRequest(this: IHookFunctions | IExecuteFunctions // @ts-ignore return await this.helpers.requestOAuth2!.call(this, 'clickUpOAuth2Api', options, oAuth2Options); } - - } catch (error) { - let errorMessage = error; - if (error.err) { - errorMessage = error.err; - } - throw new Error('ClickUp Error: ' + errorMessage); + } catch(error) { + throw new NodeApiError(this.getNode(), error); } - } export async function clickupApiRequestAllItems(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any diff --git a/packages/nodes-base/nodes/Clockify/GenericFunctions.ts b/packages/nodes-base/nodes/Clockify/GenericFunctions.ts index b8288427c3..e8fc43e6c4 100644 --- a/packages/nodes-base/nodes/Clockify/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Clockify/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function clockifyApiRequest(this: ILoadOptionsFunctions | IPollFunctions | IExecuteFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -17,7 +17,7 @@ export async function clockifyApiRequest(this: ILoadOptionsFunctions | IPollFunc const credentials = this.getCredentials('clockifyApi'); 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'; @@ -36,20 +36,9 @@ export async function clockifyApiRequest(this: ILoadOptionsFunctions | IPollFunc }; try { - return await this.helpers.request!(options); - } catch (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); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts b/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts index 65a5c2005c..5e34e76615 100644 --- a/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts @@ -3,14 +3,14 @@ import { IExecuteSingleFunctions, ILoadOptionsFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import { OptionsWithUri } from 'request'; export async function cockpitApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('cockpitApi'); if (credentials === undefined) { - throw new Error('No credentials available.'); + throw new NodeOperationError(this.getNode(), 'No credentials available.'); } let options: OptionsWithUri = { @@ -36,12 +36,7 @@ export async function cockpitApiRequest(this: IExecuteFunctions | IExecuteSingle try { return await this.helpers.request!(options); } catch (error) { - let errorMessage = error.message; - if (error.error) { - errorMessage = error.error.message || error.error.error; - } - - throw new Error(`Cockpit error [${error.statusCode}]: ` + errorMessage); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Coda/Coda.node.ts b/packages/nodes-base/nodes/Coda/Coda.node.ts index f360b825b1..b53af362ed 100644 --- a/packages/nodes-base/nodes/Coda/Coda.node.ts +++ b/packages/nodes-base/nodes/Coda/Coda.node.ts @@ -8,6 +8,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { codaApiRequest, @@ -338,8 +340,8 @@ export class Coda implements INodeType { responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs); responseData = responseData.items; } - } catch (err) { - throw new Error(`Coda Error: ${err.message}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } if (options.rawData === true) { @@ -540,8 +542,8 @@ export class Coda implements INodeType { responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs); responseData = responseData.items; } - } catch (err) { - throw new Error(`Coda Error: ${err.message}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } if (options.rawData === true) { diff --git a/packages/nodes-base/nodes/Coda/GenericFunctions.ts b/packages/nodes-base/nodes/Coda/GenericFunctions.ts index b9270b5151..2dd7691150 100644 --- a/packages/nodes-base/nodes/Coda/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Coda/GenericFunctions.ts @@ -4,12 +4,12 @@ import { IExecuteSingleFunctions, ILoadOptionsFunctions, } 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 { // tslint:disable-line:no-any const credentials = this.getCredentials('codaApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { @@ -28,12 +28,7 @@ export async function codaApiRequest(this: IExecuteFunctions | IExecuteSingleFun try { return await this.helpers.request!(options); } catch (error) { - let errorMessage = error.message; - if (error.response.body) { - errorMessage = error.response.body.message || error.response.body.Message || error.message; - } - - throw new Error('Coda Error: ' + errorMessage); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/CoinGecko/GenericFunctions.ts b/packages/nodes-base/nodes/CoinGecko/GenericFunctions.ts index d5f3e0b51a..73c35b56eb 100644 --- a/packages/nodes-base/nodes/CoinGecko/GenericFunctions.ts +++ b/packages/nodes-base/nodes/CoinGecko/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; 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); } catch (error) { - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Compression.node.ts b/packages/nodes-base/nodes/Compression.node.ts index 920570cc22..80ac7f0e6f 100644 --- a/packages/nodes-base/nodes/Compression.node.ts +++ b/packages/nodes-base/nodes/Compression.node.ts @@ -8,6 +8,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import * as fflate from 'fflate'; @@ -216,11 +217,11 @@ export class Compression implements INodeType { for (const [index, binaryPropertyName] of binaryPropertyNames.entries()) { 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 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]; @@ -270,11 +271,11 @@ export class Compression implements INodeType { for (const [index, binaryPropertyName] of binaryPropertyNames.entries()) { 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 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]; diff --git a/packages/nodes-base/nodes/Contentful/GenericFunctions.ts b/packages/nodes-base/nodes/Contentful/GenericFunctions.ts index f5a25b8c42..24cf806560 100644 --- a/packages/nodes-base/nodes/Contentful/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Contentful/GenericFunctions.ts @@ -9,14 +9,14 @@ import { } from 'request'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function contentfulApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('contentfulApi'); 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; @@ -39,7 +39,7 @@ export async function contentfulApiRequest(this: IExecuteFunctions | IExecuteSin try { return await this.helpers.request!(options); } catch (error) { - throw new Error(`Contentful error response [${error.statusCode}]: ${error.error.message}`); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/ConvertKit/GenericFunctions.ts b/packages/nodes-base/nodes/ConvertKit/GenericFunctions.ts index 2f034f6892..50741c267a 100644 --- a/packages/nodes-base/nodes/ConvertKit/GenericFunctions.ts +++ b/packages/nodes-base/nodes/ConvertKit/GenericFunctions.ts @@ -10,7 +10,9 @@ import { import { IDataObject, - IHookFunctions + IHookFunctions, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; 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'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { @@ -51,17 +53,8 @@ export async function convertKitApiRequest(this: IExecuteFunctions | IExecuteSin } try { - return await this.helpers.request!(options); - } catch (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}`); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts b/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts index cfaf575f38..eaf48ed1ee 100644 --- a/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts +++ b/packages/nodes-base/nodes/Copper/CopperTrigger.node.ts @@ -116,7 +116,7 @@ export class CopperTrigger implements INodeType { const endpoint = `/webhooks/${webhookData.webhookId}`; try { await copperApiRequest.call(this, 'GET', endpoint); - } catch (err) { + } catch (error) { return false; } return true; diff --git a/packages/nodes-base/nodes/Copper/GenericFunctions.ts b/packages/nodes-base/nodes/Copper/GenericFunctions.ts index 2fbf4f315d..8d91f53285 100644 --- a/packages/nodes-base/nodes/Copper/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Copper/GenericFunctions.ts @@ -17,6 +17,7 @@ import { import { ICredentialDataDecryptedObject, IDataObject, + NodeApiError, } from 'n8n-workflow'; import { @@ -72,12 +73,7 @@ export async function copperApiRequest( try { return await this.helpers.request!(options); } catch (error) { - let errorMessage = error.message; - if (error.response.body?.message) { - errorMessage = error.response.body.message; - } - - throw new Error(`Copper error response [${error.statusCode}]: ${errorMessage}`); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Cortex/Cortex.node.ts b/packages/nodes-base/nodes/Cortex/Cortex.node.ts index 27a2d2c9e1..d2b934fcdc 100644 --- a/packages/nodes-base/nodes/Cortex/Cortex.node.ts +++ b/packages/nodes-base/nodes/Cortex/Cortex.node.ts @@ -23,6 +23,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -231,13 +232,13 @@ export class Cortex implements INodeType { const item = items[i]; 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; 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); @@ -386,13 +387,13 @@ export class Cortex implements INodeType { const item = items[i]; 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; 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; @@ -415,12 +416,12 @@ export class Cortex implements INodeType { const item = items[i]; 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; 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); diff --git a/packages/nodes-base/nodes/Cortex/GenericFunctions.ts b/packages/nodes-base/nodes/Cortex/GenericFunctions.ts index c0945e932b..a44837b77f 100644 --- a/packages/nodes-base/nodes/Cortex/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Cortex/GenericFunctions.ts @@ -16,7 +16,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import * as moment from 'moment'; @@ -26,7 +26,7 @@ export async function cortexApiRequest(this: IHookFunctions | IExecuteFunctions const credentials = this.getCredentials('cortexApi'); 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}` }); @@ -53,10 +53,7 @@ export async function cortexApiRequest(this: IHookFunctions | IExecuteFunctions try { return await this.helpers.request!(options); } catch (error) { - if (error.error) { - const errorMessage = `Cortex error response [${error.statusCode}]: ${error.error.message}`; - throw new Error(errorMessage); - } else throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/CrateDb/CrateDb.node.ts b/packages/nodes-base/nodes/CrateDb/CrateDb.node.ts index a93c0c222d..a2577282b8 100644 --- a/packages/nodes-base/nodes/CrateDb/CrateDb.node.ts +++ b/packages/nodes-base/nodes/CrateDb/CrateDb.node.ts @@ -4,6 +4,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -188,7 +189,7 @@ export class CrateDb implements INodeType { const credentials = this.getCredentials('crateDb'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const pgp = pgPromise(); @@ -262,7 +263,7 @@ export class CrateDb implements INodeType { updateKeyValue = item.json[updateKey] as string | number; 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); @@ -277,7 +278,7 @@ export class CrateDb implements INodeType { returnItems = this.helpers.returnJsonArray(getItemCopy(items, columns) as IDataObject[]); } else { 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 diff --git a/packages/nodes-base/nodes/CustomerIo/CustomerIo.node.ts b/packages/nodes-base/nodes/CustomerIo/CustomerIo.node.ts index f97efc052f..fd6f6f6146 100644 --- a/packages/nodes-base/nodes/CustomerIo/CustomerIo.node.ts +++ b/packages/nodes-base/nodes/CustomerIo/CustomerIo.node.ts @@ -6,6 +6,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { customerIoApiRequest, @@ -131,7 +132,7 @@ export class CustomerIo implements INodeType { Object.assign(body, JSON.parse(additionalFieldsJson)); } else { - throw new Error('Additional fields must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON'); } } } else { @@ -175,7 +176,7 @@ export class CustomerIo implements INodeType { Object.assign(body, JSON.parse(additionalFieldsJson)); } else { - throw new Error('Additional fields must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON'); } } } else { @@ -238,7 +239,7 @@ export class CustomerIo implements INodeType { if (validateJSON(additionalFieldsJson) !== undefined) { Object.assign(body, JSON.parse(additionalFieldsJson)); } else { - throw new Error('Additional fields must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON'); } } } else { @@ -283,7 +284,7 @@ export class CustomerIo implements INodeType { Object.assign(body, JSON.parse(additionalFieldsJson)); } else { - throw new Error('Additional fields must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON'); } } } else { diff --git a/packages/nodes-base/nodes/CustomerIo/CustomerIoTrigger.node.ts b/packages/nodes-base/nodes/CustomerIo/CustomerIoTrigger.node.ts index eb258a20be..070df18dc3 100644 --- a/packages/nodes-base/nodes/CustomerIo/CustomerIoTrigger.node.ts +++ b/packages/nodes-base/nodes/CustomerIo/CustomerIoTrigger.node.ts @@ -308,7 +308,7 @@ export class CustomerIoTrigger implements INodeType { const endpoint = `/reporting_webhooks/${webhookData.webhookId}`; try { await customerIoApiRequest.call(this, 'DELETE', endpoint, {}, 'beta'); - } catch (e) { + } catch (error) { return false; } delete webhookData.webhookId; diff --git a/packages/nodes-base/nodes/CustomerIo/GenericFunctions.ts b/packages/nodes-base/nodes/CustomerIo/GenericFunctions.ts index c58bea8883..609baeb52d 100644 --- a/packages/nodes-base/nodes/CustomerIo/GenericFunctions.ts +++ b/packages/nodes-base/nodes/CustomerIo/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'request'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import { @@ -20,7 +20,7 @@ export async function customerIoApiRequest(this: IHookFunctions | IExecuteFuncti const credentials = this.getCredentials('customerIoApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } query = query || {}; @@ -51,19 +51,7 @@ export async function customerIoApiRequest(this: IHookFunctions | IExecuteFuncti try { return await this.helpers.request!(options); } catch (error) { - if (error.statusCode === 401) { - // 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; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/DateTime.node.ts b/packages/nodes-base/nodes/DateTime.node.ts index 019ff3d4a5..a5dabb43a0 100644 --- a/packages/nodes-base/nodes/DateTime.node.ts +++ b/packages/nodes-base/nodes/DateTime.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; @@ -244,7 +245,7 @@ export class DateTime implements INodeType { continue; } 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)) { diff --git a/packages/nodes-base/nodes/DeepL/GenericFunctions.ts b/packages/nodes-base/nodes/DeepL/GenericFunctions.ts index 7b8f9a1004..0baa69665a 100644 --- a/packages/nodes-base/nodes/DeepL/GenericFunctions.ts +++ b/packages/nodes-base/nodes/DeepL/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function deepLApiRequest( @@ -45,7 +45,7 @@ export async function deepLApiRequest( const credentials = this.getCredentials('deepLApi'); 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; @@ -53,10 +53,6 @@ export async function deepLApiRequest( return await this.helpers.request!(options); } catch (error) { - if (error?.response?.body?.message) { - // Try to return the error prettier - throw new Error(`DeepL error response [${error.statusCode}]: ${error.response.body.message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Demio/GenericFunctions.ts b/packages/nodes-base/nodes/Demio/GenericFunctions.ts index 3d96faacca..04b7a9d6cc 100644 --- a/packages/nodes-base/nodes/Demio/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Demio/GenericFunctions.ts @@ -10,14 +10,14 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } 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 { // tslint:disable-line:no-any try { const credentials = this.getCredentials('demioApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { headers: { @@ -35,14 +35,6 @@ export async function demioApiRequest(this: IHookFunctions | IExecuteFunctions | return await this.helpers.request!(options); } catch (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; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Discord/Discord.node.ts b/packages/nodes-base/nodes/Discord/Discord.node.ts index 995c0e7e08..36531aa9c3 100644 --- a/packages/nodes-base/nodes/Discord/Discord.node.ts +++ b/packages/nodes-base/nodes/Discord/Discord.node.ts @@ -5,6 +5,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; export class Discord implements INodeType { @@ -86,15 +88,14 @@ export class Discord implements INodeType { }, get(error, 'response.body.retry_after', 150)); }); } else { - // If it's another error code then return the JSON response - throw error; + throw new NodeApiError(this.getNode(), error); } } } while (--maxTries); 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}); diff --git a/packages/nodes-base/nodes/Discourse/GenericFunctions.ts b/packages/nodes-base/nodes/Discourse/GenericFunctions.ts index 0a8057bd08..a5c5acd55e 100644 --- a/packages/nodes-base/nodes/Discourse/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Discourse/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function discourseApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise { // tslint:disable-line:no-any @@ -35,15 +35,7 @@ export async function discourseApiRequest(this: IExecuteFunctions | IExecuteSing //@ts-ignore return await this.helpers.request.call(this, options); } catch (error) { - if (error.response && error.response.body && error.response.body.errors) { - - const errors = error.response.body.errors; - // Try to return the error prettier - throw new Error( - `Discourse error response [${error.statusCode}]: ${errors.join('|')}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Disqus/Disqus.node.ts b/packages/nodes-base/nodes/Disqus/Disqus.node.ts index 8278f39970..dc92b80fb6 100644 --- a/packages/nodes-base/nodes/Disqus/Disqus.node.ts +++ b/packages/nodes-base/nodes/Disqus/Disqus.node.ts @@ -6,6 +6,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { disqusApiRequest, disqusApiRequestAllItems } from './GenericFunctions'; @@ -771,11 +772,11 @@ export class Disqus implements INodeType { } } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } } diff --git a/packages/nodes-base/nodes/Disqus/GenericFunctions.ts b/packages/nodes-base/nodes/Disqus/GenericFunctions.ts index b9bb22207f..2b1414989c 100644 --- a/packages/nodes-base/nodes/Disqus/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Disqus/GenericFunctions.ts @@ -5,7 +5,7 @@ import { IHookFunctions, ILoadOptionsFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function disqusApiRequest( this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, @@ -19,7 +19,7 @@ export async function disqusApiRequest( const credentials = this.getCredentials('disqusApi') as IDataObject; qs.api_key = credentials.accessToken; 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 @@ -46,21 +46,9 @@ export async function disqusApiRequest( delete options.body; } try { - const result = await this.helpers.request!(options); - return result; + return await this.helpers.request!(options); } catch (error) { - if (error.statusCode === 401) { - // 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; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Drift/GenericFunctions.ts b/packages/nodes-base/nodes/Drift/GenericFunctions.ts index f021a01d37..9b288c04eb 100644 --- a/packages/nodes-base/nodes/Drift/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Drift/GenericFunctions.ts @@ -8,7 +8,9 @@ import { import { IDataObject, IHookFunctions, - IWebhookFunctions + IWebhookFunctions, + NodeApiError, + NodeOperationError, } 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 { // tslint:disable-line:no-any @@ -36,7 +38,7 @@ export async function driftApiRequest(this: IExecuteFunctions | IWebhookFunction const credentials = this.getCredentials('driftApi'); 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}`; @@ -46,11 +48,6 @@ export async function driftApiRequest(this: IExecuteFunctions | IWebhookFunction return await this.helpers.requestOAuth2!.call(this, 'driftOAuth2Api', options); } } catch (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; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Dropbox/Dropbox.node.ts b/packages/nodes-base/nodes/Dropbox/Dropbox.node.ts index 17be478c36..fcb3b2f7a5 100644 --- a/packages/nodes-base/nodes/Dropbox/Dropbox.node.ts +++ b/packages/nodes-base/nodes/Dropbox/Dropbox.node.ts @@ -8,6 +8,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -849,13 +850,13 @@ export class Dropbox implements INodeType { const item = items[i]; 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; 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); @@ -980,7 +981,7 @@ export class Dropbox implements INodeType { endpoint = 'https://api.dropboxapi.com/2/files/move_v2'; } } 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') { diff --git a/packages/nodes-base/nodes/Dropbox/GenericFunctions.ts b/packages/nodes-base/nodes/Dropbox/GenericFunctions.ts index ca7e4829bb..caf476bee9 100644 --- a/packages/nodes-base/nodes/Dropbox/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Dropbox/GenericFunctions.ts @@ -8,7 +8,7 @@ import { } from 'request'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; /** @@ -51,20 +51,7 @@ export async function dropboxApiRequest(this: IHookFunctions | IExecuteFunctions return await this.helpers.requestOAuth2.call(this, 'dropboxOAuth2Api', options); } } catch (error) { - if (error.statusCode === 401) { - // 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; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/ERPNext/ERPNext.node.ts b/packages/nodes-base/nodes/ERPNext/ERPNext.node.ts index b5fc28a8bb..befe2af7c8 100644 --- a/packages/nodes-base/nodes/ERPNext/ERPNext.node.ts +++ b/packages/nodes-base/nodes/ERPNext/ERPNext.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -206,7 +207,7 @@ export class ERPNext implements INodeType { const properties = this.getNodeParameter('properties', i) as DocumentProperties; 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 => { @@ -242,7 +243,7 @@ export class ERPNext implements INodeType { const properties = this.getNodeParameter('properties', i) as DocumentProperties; 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 => { diff --git a/packages/nodes-base/nodes/ERPNext/GenericFunctions.ts b/packages/nodes-base/nodes/ERPNext/GenericFunctions.ts index b8da3d99d6..08de44e8e0 100644 --- a/packages/nodes-base/nodes/ERPNext/GenericFunctions.ts +++ b/packages/nodes-base/nodes/ERPNext/GenericFunctions.ts @@ -10,7 +10,9 @@ import { import { IDataObject, IHookFunctions, - IWebhookFunctions + IWebhookFunctions, + NodeApiError, + NodeOperationError } from 'n8n-workflow'; export async function erpNextApiRequest( @@ -26,7 +28,7 @@ export async function erpNextApiRequest( const credentials = this.getCredentials('erpNextApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { @@ -56,27 +58,15 @@ export async function erpNextApiRequest( } catch (error) { if (error.statusCode === 403) { - throw new Error( - `ERPNext error response [${error.statusCode}]: DocType unavailable.`, - ); + throw new NodeApiError(this.getNode(), { message: `DocType unavailable.` }); } if (error.statusCode === 307) { - throw new Error( - `ERPNext error response [${error.statusCode}]: Please ensure the subdomain is correct.`, - ); + throw new NodeApiError(this.getNode(), { message:`Please ensure the subdomain is correct.` }); } - let errorMessages; - 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 new NodeApiError(this.getNode(), error); - throw error; } } diff --git a/packages/nodes-base/nodes/EditImage.node.ts b/packages/nodes-base/nodes/EditImage.node.ts index 0c871e01e8..f23f42bb7b 100644 --- a/packages/nodes-base/nodes/EditImage.node.ts +++ b/packages/nodes-base/nodes/EditImage.node.ts @@ -10,6 +10,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import * as gm from 'gm'; import { file } from 'tmp-promise'; @@ -1041,7 +1042,7 @@ export class EditImage implements INodeType { requiredOperationParameters[operation].forEach(parameterName => { try { operationParameters[parameterName] = this.getNodeParameter(parameterName, itemIndex); - } catch (e) {} + } catch (error) {} }); operations = [ @@ -1055,11 +1056,11 @@ export class EditImage implements INodeType { if (operations[0].operation !== 'create') { // "create" generates a new image so does not require any incoming data. 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) { - 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)); @@ -1095,7 +1096,7 @@ export class EditImage implements INodeType { const geometryString = (positionX >= 0 ? '+' : '') + positionX + (positionY >= 0 ? '+' : '') + positionY; 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(); diff --git a/packages/nodes-base/nodes/Egoi/GenericFunctions.ts b/packages/nodes-base/nodes/Egoi/GenericFunctions.ts index 7694223ed9..e7447d9084 100644 --- a/packages/nodes-base/nodes/Egoi/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Egoi/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; interface IContact { @@ -58,20 +58,7 @@ export async function egoiApiRequest(this: IHookFunctions | IExecuteFunctions | return await this.helpers.request!(options); } catch (error) { - let errorMessage; - - 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; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/EmailReadImap.node.ts b/packages/nodes-base/nodes/EmailReadImap.node.ts index 62fdfce194..fed766849d 100644 --- a/packages/nodes-base/nodes/EmailReadImap.node.ts +++ b/packages/nodes-base/nodes/EmailReadImap.node.ts @@ -7,6 +7,7 @@ import { INodeType, INodeTypeDescription, ITriggerResponse, + NodeOperationError, } from 'n8n-workflow'; import { @@ -168,7 +169,7 @@ export class EmailReadImap implements INodeType { const credentials = this.getCredentials('imap'); 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; @@ -181,8 +182,8 @@ export class EmailReadImap implements INodeType { if (options.customEmailConfig !== undefined) { try { searchCriteria = JSON.parse(options.customEmailConfig as string); - } catch (err) { - throw new Error(`Custom email config is not valid JSON.`); + } catch (error) { + 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: '' }); 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); @@ -337,7 +338,7 @@ export class EmailReadImap implements INodeType { const part = lodash.find(message.parts, { which: 'TEXT' }); 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 newEmail = { diff --git a/packages/nodes-base/nodes/EmailSend.node.ts b/packages/nodes-base/nodes/EmailSend.node.ts index 6028d6c547..cbe1dd5cbe 100644 --- a/packages/nodes-base/nodes/EmailSend.node.ts +++ b/packages/nodes-base/nodes/EmailSend.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { createTransport } from 'nodemailer'; @@ -148,7 +149,7 @@ export class EmailSend implements INodeType { const credentials = this.getCredentials('smtp'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const connectionOptions: SMTPTransport.Options = { diff --git a/packages/nodes-base/nodes/Emelia/GenericFunctions.ts b/packages/nodes-base/nodes/Emelia/GenericFunctions.ts index e5577b9680..916da27c98 100644 --- a/packages/nodes-base/nodes/Emelia/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Emelia/GenericFunctions.ts @@ -6,6 +6,7 @@ import { import { IHookFunctions, INodePropertyOptions, + NodeApiError, } from 'n8n-workflow'; /** @@ -18,7 +19,7 @@ export async function emeliaGraphqlRequest( const response = await emeliaApiRequest.call(this, 'POST', '/graphql', body); if (response.errors) { - throw new Error(`Emelia error message: ${response.errors[0].message}`); + throw new NodeApiError(this.getNode(), response); } return response; @@ -48,19 +49,9 @@ export async function emeliaApiRequest( }; try { - return await this.helpers.request!.call(this, options); - } catch (error) { - - if (error?.response?.body?.error) { - const { error: errorMessage } = error.response.body; - throw new Error( - `Emelia error response [${error.statusCode}]: ${errorMessage}`, - ); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts b/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts index 26d8d7c2b1..95cd14ab37 100644 --- a/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts +++ b/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts @@ -10,6 +10,7 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeApiError, } from 'n8n-workflow'; import { @@ -292,7 +293,7 @@ export class EventbriteTrigger implements INodeType { const req = this.getRequestObject(); if (req.body.api_url === undefined) { - throw new Error('The received data does not contain required "api_url" property!'); + throw new NodeApiError(this.getNode(), req.body, { message: 'The received data does not contain required "api_url" property!' }); } const resolveData = this.getNodeParameter('resolveData', false) as boolean; diff --git a/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts b/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts index 3916aece69..8490896b1e 100644 --- a/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts @@ -11,7 +11,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function eventbriteApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -34,7 +34,7 @@ export async function eventbriteApiRequest(this: IHookFunctions | IExecuteFuncti if (authenticationMethod === 'privateKey') { const credentials = this.getCredentials('eventbriteApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } options.headers!['Authorization'] = `Bearer ${credentials.apiKey}`; @@ -44,11 +44,7 @@ export async function eventbriteApiRequest(this: IHookFunctions | IExecuteFuncti return await this.helpers.requestOAuth2!.call(this, 'eventbriteOAuth2Api', options); } } catch (error) { - let errorMessage = error.message; - if (error.response.body && error.response.body.error_description) { - errorMessage = error.response.body.error_description; - } - throw new Error('Eventbrite Error: ' + errorMessage); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/ExecuteWorkflow.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow.node.ts index 4e49883310..e4883cd4e8 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow.node.ts @@ -12,6 +12,7 @@ import { INodeType, INodeTypeDescription, IWorkflowBase, + NodeOperationError, } from 'n8n-workflow'; @@ -164,7 +165,7 @@ export class ExecuteWorkflow implements INodeType { workflowJson = await fsReadFileAsync(workflowPath, { encoding: 'utf8' }) as string; } catch (error) { if (error.code === 'ENOENT') { - throw new Error(`The file "${workflowPath}" could not be found.`); + throw new NodeOperationError(this.getNode(), `The file "${workflowPath}" could not be found.`); } throw error; diff --git a/packages/nodes-base/nodes/Facebook/FacebookGraphApi.node.ts b/packages/nodes-base/nodes/Facebook/FacebookGraphApi.node.ts index 1b5582de7f..0beabbb9b6 100644 --- a/packages/nodes-base/nodes/Facebook/FacebookGraphApi.node.ts +++ b/packages/nodes-base/nodes/Facebook/FacebookGraphApi.node.ts @@ -8,6 +8,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -365,7 +367,7 @@ export class FacebookGraphApi implements INodeType { if (sendBinaryData) { const item = items[itemIndex]; 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 binaryPropertyNameFull = this.getNodeParameter('binaryPropertyName', itemIndex) as string; @@ -379,7 +381,7 @@ export class FacebookGraphApi implements INodeType { } 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 binaryProperty = item.binary[binaryPropertyName] as IBinaryData; @@ -400,7 +402,7 @@ export class FacebookGraphApi implements INodeType { response = await this.helpers.request(requestOptions); } catch (error) { if (this.continueOnFail() === false) { - throw error; + throw new NodeApiError(this.getNode(), error); } let errorItem; @@ -425,7 +427,7 @@ export class FacebookGraphApi implements INodeType { if (typeof response === 'string') { if (this.continueOnFail() === false) { - throw new Error('Response body is not valid JSON.'); + throw new NodeOperationError(this.getNode(), 'Response body is not valid JSON.'); } returnItems.push({ json: { message: response } }); diff --git a/packages/nodes-base/nodes/Facebook/FacebookTrigger.node.ts b/packages/nodes-base/nodes/Facebook/FacebookTrigger.node.ts index 278ca729bc..d532bfc1a9 100644 --- a/packages/nodes-base/nodes/Facebook/FacebookTrigger.node.ts +++ b/packages/nodes-base/nodes/Facebook/FacebookTrigger.node.ts @@ -8,6 +8,7 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeApiError, } from 'n8n-workflow'; import * as uuid from 'uuid/v4'; @@ -192,7 +193,7 @@ export class FacebookTrigger implements INodeType { if (responseData.success !== true) { // Facebook did not return success, so something went wrong - throw new Error('Facebook webhook creation response did not contain the expected data.'); + throw new NodeApiError(this.getNode(), responseData, { message: 'Facebook webhook creation response did not contain the expected data.' }); } return true; }, @@ -202,7 +203,7 @@ export class FacebookTrigger implements INodeType { try { await facebookApiRequest.call(this, 'DELETE', `/${appId}/subscriptions`, { object: snakeCase(object) }); - } catch (e) { + } catch (error) { return false; } return true; diff --git a/packages/nodes-base/nodes/Facebook/GenericFunctions.ts b/packages/nodes-base/nodes/Facebook/GenericFunctions.ts index 20e16ecf49..609cc14042 100644 --- a/packages/nodes-base/nodes/Facebook/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Facebook/GenericFunctions.ts @@ -11,7 +11,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function facebookApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -41,13 +41,6 @@ export async function facebookApiRequest(this: IHookFunctions | IExecuteFunction try { return await this.helpers.request!(options); } catch (error) { - - if (error.response.body && error.response.body.error) { - const message = error.response.body.error.message; - throw new Error( - `Facebook Trigger error response [${error.statusCode}]: ${message}`, - ); - } - throw new Error(error); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts index 5efa053cb4..402ff35f9d 100644 --- a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts +++ b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts @@ -4,6 +4,7 @@ import { INodeExecutionData, INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; @@ -680,8 +681,8 @@ export class FileMaker implements INodeType { try { returnData = await layoutsApiRequest.call(this); - } catch (err) { - throw new Error(`FileMaker Error: ${err}`); + } catch (error) { + throw new NodeOperationError(this.getNode(), `FileMaker Error: ${error}`); } return returnData; @@ -696,8 +697,8 @@ export class FileMaker implements INodeType { let layouts; try { layouts = await layoutsApiRequest.call(this); - } catch (err) { - throw new Error(`FileMaker Error: ${err}`); + } catch (error) { + throw new NodeOperationError(this.getNode(), `FileMaker Error: ${error}`); } for (const layout of layouts) { returnData.push({ @@ -714,8 +715,8 @@ export class FileMaker implements INodeType { let fields; try { fields = await getFields.call(this); - } catch (err) { - throw new Error(`FileMaker Error: ${err}`); + } catch (error) { + throw new NodeOperationError(this.getNode(), `FileMaker Error: ${error}`); } for (const field of fields) { returnData.push({ @@ -732,8 +733,8 @@ export class FileMaker implements INodeType { let scripts; try { scripts = await getScripts.call(this); - } catch (err) { - throw new Error(`FileMaker Error: ${err}`); + } catch (error) { + throw new NodeOperationError(this.getNode(), `FileMaker Error: ${error}`); } for (const script of scripts) { if (!script.isFolder) { @@ -752,8 +753,8 @@ export class FileMaker implements INodeType { let portals; try { portals = await getPortals.call(this); - } catch (err) { - throw new Error(`FileMaker Error: ${err}`); + } catch (error) { + throw new NodeOperationError(this.getNode(), `FileMaker Error: ${error}`); } Object.keys(portals).forEach((portal) => { returnData.push({ @@ -775,14 +776,14 @@ export class FileMaker implements INodeType { const credentials = this.getCredentials('fileMaker'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let token; try { token = await getToken.call(this); - } catch (e) { - throw new Error(`Login fail: ${e}`); + } catch (error) { + throw new NodeOperationError(this.getNode(), `Login fail: ${error}`); } let requestOptions: OptionsWithUri; @@ -886,7 +887,7 @@ export class FileMaker implements INodeType { ...parseScripts.call(this, i), }; } else { - throw new Error(`The action "${action}" is not implemented yet!`); + throw new NodeOperationError(this.getNode(), `The action "${action}" is not implemented yet!`); } // Now that the options are all set make the actual http request @@ -898,13 +899,18 @@ export class FileMaker implements INodeType { } if (typeof response === 'string') { - throw new Error('Response body is not valid JSON. Change "Response Format" to "String"'); + throw new NodeOperationError(this.getNode(), 'Response body is not valid JSON. Change "Response Format" to "String"'); } returnData.push({json: response}); } } catch (error) { await logout.call(this, token); - throw new Error(`The action "${error.message}" is not implemented yet!`); + + if (error.node) { + throw error; + } + + throw new NodeOperationError(this.getNode(), `The action "${error.message}" is not implemented yet!`); } return this.prepareOutputData(returnData); diff --git a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts index d11047b41b..f2f979cb5f 100644 --- a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts +++ b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts @@ -5,7 +5,7 @@ import { } from 'n8n-core'; import { - IDataObject, INodePropertyOptions, + IDataObject, INodePropertyOptions, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import {OptionsWithUri} from 'request'; @@ -42,7 +42,7 @@ export async function layoutsApiRequest(this: ILoadOptionsFunctions | IExecuteFu const credentials = this.getCredentials('fileMaker'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const host = credentials.host as string; const db = credentials.db as string; @@ -63,8 +63,7 @@ export async function layoutsApiRequest(this: ILoadOptionsFunctions | IExecuteFu items.sort((a, b) => a.name > b.name ? 0 : 1); return items; } catch (error) { - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } @@ -94,7 +93,7 @@ export async function getFields(this: ILoadOptionsFunctions): Promise { // const layout = this.getCurrentNodeParameter('layout') as string; if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const host = credentials.host as string; const db = credentials.db as string; @@ -130,7 +129,7 @@ export async function getPortals(this: ILoadOptionsFunctions): Promise { // const layout = this.getCurrentNodeParameter('layout') as string; if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const host = credentials.host as string; const db = credentials.db as string; @@ -165,7 +164,7 @@ export async function getScripts(this: ILoadOptionsFunctions): Promise { // const credentials = this.getCredentials('fileMaker'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const host = credentials.host as string; const db = credentials.db as string; @@ -210,7 +209,7 @@ function parseScriptsList(scripts: ScriptObject[]): INodePropertyOptions[] { export async function getToken(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('fileMaker'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const host = credentials.host as string; @@ -247,28 +246,19 @@ export async function getToken(this: ILoadOptionsFunctions | IExecuteFunctions | const response = await this.helpers.request!(requestOptions); if (typeof response === 'string') { - throw new Error('Response body is not valid JSON. Change "Response Format" to "String"'); + throw new NodeOperationError(this.getNode(), 'Response body is not valid JSON. Change "Response Format" to "String"'); } return response.response.token; } catch (error) { - let errorMessage; - if (error.response) { - errorMessage = error.response.body.messages[0].message + '(' + error.response.body.messages[0].message + ')'; - } else { - errorMessage = `${error.message} (${error.name})`; - } - if (errorMessage !== undefined) { - throw errorMessage; - } - throw error.message; + throw new NodeApiError(this.getNode(), error); } } export async function logout(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions, token: string): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('fileMaker'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const host = credentials.host as string; @@ -290,7 +280,7 @@ export async function logout(this: ILoadOptionsFunctions | IExecuteFunctions | I const response = await this.helpers.request!(requestOptions); if (typeof response === 'string') { - throw new Error('Response body is not valid JSON. Change "Response Format" to "String"'); + throw new NodeOperationError(this.getNode(), 'Response body is not valid JSON. Change "Response Format" to "String"'); } return response; diff --git a/packages/nodes-base/nodes/Flow/Flow.node.ts b/packages/nodes-base/nodes/Flow/Flow.node.ts index eb6f6728f0..79bc09f985 100644 --- a/packages/nodes-base/nodes/Flow/Flow.node.ts +++ b/packages/nodes-base/nodes/Flow/Flow.node.ts @@ -6,6 +6,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { flowApiRequest, @@ -64,7 +66,7 @@ export class Flow implements INodeType { const credentials = this.getCredentials('flowApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const items = this.getInputData(); @@ -135,8 +137,8 @@ export class Flow implements INodeType { try { responseData = await flowApiRequest.call(this, 'POST', '/tasks', body); responseData = responseData.task; - } catch (err) { - throw new Error(`Flow Error: ${err.message}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.getflow.com/api/#tasks_update-a-task @@ -203,8 +205,8 @@ export class Flow implements INodeType { try { responseData = await flowApiRequest.call(this, 'PUT', `/tasks/${taskId}`, body); responseData = responseData.task; - } catch (err) { - throw new Error(`Flow Error: ${err.message}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.getflow.com/api/#tasks_get-task @@ -217,8 +219,8 @@ export class Flow implements INodeType { } try { responseData = await flowApiRequest.call(this,'GET', `/tasks/${taskId}`, {}, qs); - } catch (err) { - throw new Error(`Flow Error: ${err.message}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.getflow.com/api/#tasks_get-tasks @@ -261,8 +263,8 @@ export class Flow implements INodeType { responseData = await flowApiRequest.call(this, 'GET', '/tasks', {}, qs); responseData = responseData.tasks; } - } catch (err) { - throw new Error(`Flow Error: ${err.message}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } } diff --git a/packages/nodes-base/nodes/Flow/FlowTrigger.node.ts b/packages/nodes-base/nodes/Flow/FlowTrigger.node.ts index e9b7240888..062dbe1630 100644 --- a/packages/nodes-base/nodes/Flow/FlowTrigger.node.ts +++ b/packages/nodes-base/nodes/Flow/FlowTrigger.node.ts @@ -8,6 +8,7 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeOperationError, } from 'n8n-workflow'; import { @@ -111,7 +112,7 @@ export class FlowTrigger implements INodeType { const credentials = this.getCredentials('flowApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let webhooks; @@ -128,8 +129,8 @@ export class FlowTrigger implements INodeType { try { webhooks = await flowApiRequest.call(this, 'GET', endpoint, {}, qs); webhooks = webhooks.integration_webhooks; - } catch (e) { - throw e; + } catch (error) { + throw error; } for (const webhook of webhooks) { // @ts-ignore @@ -145,7 +146,7 @@ export class FlowTrigger implements INodeType { const credentials = this.getCredentials('flowApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let resourceIds, body, responseData; @@ -189,7 +190,7 @@ export class FlowTrigger implements INodeType { const credentials = this.getCredentials('flowApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const qs: IDataObject = {}; @@ -202,7 +203,7 @@ export class FlowTrigger implements INodeType { const endpoint = `/integration_webhooks/${webhookId}`; try { await flowApiRequest.call(this, 'DELETE', endpoint, {}, qs); - } catch (e) { + } catch (error) { return false; } } diff --git a/packages/nodes-base/nodes/Flow/GenericFunctions.ts b/packages/nodes-base/nodes/Flow/GenericFunctions.ts index 20d045385a..447d9eca06 100644 --- a/packages/nodes-base/nodes/Flow/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Flow/GenericFunctions.ts @@ -5,12 +5,12 @@ import { IHookFunctions, ILoadOptionsFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function flowApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('flowApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { @@ -29,12 +29,7 @@ export async function flowApiRequest(this: IHookFunctions | IExecuteFunctions | try { return await this.helpers.request!(options); } catch (error) { - let errorMessage = error.message; - if (error.response.body) { - errorMessage = error.response.body.message || error.response.body.Message || error.message; - } - - throw new Error(errorMessage); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts b/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts index 3b7d26f405..57f6aa1041 100644 --- a/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts +++ b/packages/nodes-base/nodes/Freshdesk/Freshdesk.node.ts @@ -5,6 +5,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -1169,7 +1170,7 @@ export class Freshdesk implements INodeType { if (requester === 'requesterId') { // @ts-ignore if (isNaN(value)) { - throw new Error('Requester Id must be a number'); + throw new NodeOperationError(this.getNode(), 'Requester Id must be a number'); } body.requester_id = parseInt(value, 10); } else if (requester === 'email') { @@ -1254,7 +1255,7 @@ export class Freshdesk implements INodeType { if (updateFields.requester === 'requesterId') { // @ts-ignore if (isNaN(parseInt(value, 10))) { - throw new Error('Requester Id must be a number'); + throw new NodeOperationError(this.getNode(), 'Requester Id must be a number'); } body.requester_id = parseInt(value as string, 10); } else if (updateFields.requester === 'email') { diff --git a/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts b/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts index 71c9c61108..dea3ef15a0 100644 --- a/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Freshdesk/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function freshdeskApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -17,7 +17,7 @@ export async function freshdeskApiRequest(this: IExecuteFunctions | ILoadOptions const credentials = this.getCredentials('freshdeskApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const apiKey = `${credentials.apiKey}:X`; @@ -45,15 +45,7 @@ export async function freshdeskApiRequest(this: IExecuteFunctions | ILoadOptions try { return await this.helpers.request!(options); } catch (error) { - if (error.response) { - let errorMessage = error.response.body.message || error.response.body.description || error.message; - if (error.response.body && error.response.body.errors) { - errorMessage = error.response.body.errors.map((err: IDataObject) => `"${err.field}" => ${err.message}`).join(', '); - } - throw new Error(`Freshdesk error response [${error.statusCode}]: ${errorMessage}`); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Ftp.node.ts b/packages/nodes-base/nodes/Ftp.node.ts index ae728dac7c..12835376e0 100644 --- a/packages/nodes-base/nodes/Ftp.node.ts +++ b/packages/nodes-base/nodes/Ftp.node.ts @@ -7,7 +7,9 @@ import { IDataObject, INodeExecutionData, INodeType, - INodeTypeDescription + INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { basename, @@ -338,7 +340,7 @@ export class Ftp implements INodeType { } if (credentials === undefined) { - throw new Error('Failed to get credentials!'); + throw new NodeOperationError(this.getNode(), 'Failed to get credentials!'); } let ftp : ftpClient; @@ -446,13 +448,13 @@ export class Ftp implements INodeType { const item = items[i]; 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; 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!`); } const buffer = Buffer.from(item.binary[propertyNameUpload].data, BINARY_ENCODING) as Buffer; @@ -535,13 +537,13 @@ export class Ftp implements INodeType { const item = items[i]; 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; 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!`); } const buffer = Buffer.from(item.binary[propertyNameUpload].data, BINARY_ENCODING) as Buffer; @@ -554,7 +556,7 @@ export class Ftp implements INodeType { await ftp!.mkdir(dirPath, true); await ftp!.put(buffer, remotePath); } else { - throw new Error(error); + throw new NodeApiError(this.getNode(), error); } } } else { @@ -568,7 +570,7 @@ export class Ftp implements INodeType { await ftp!.mkdir(dirPath, true); await ftp!.put(buffer, remotePath); } else { - throw new Error(error); + throw new NodeApiError(this.getNode(), error); } } } diff --git a/packages/nodes-base/nodes/Function.node.ts b/packages/nodes-base/nodes/Function.node.ts index 663f94938c..91f50f0617 100644 --- a/packages/nodes-base/nodes/Function.node.ts +++ b/packages/nodes-base/nodes/Function.node.ts @@ -3,6 +3,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; const { NodeVM } = require('vm2'); @@ -85,28 +86,28 @@ export class Function implements INodeType { try { // Execute the function code items = (await vm.run(`module.exports = async function() {${functionCode}}()`, __dirname)); - } catch (e) { - return Promise.reject(e); + } catch (error) { + return Promise.reject(error); } // Do very basic validation of the data if (items === undefined) { - throw new Error('No data got returned. Always return an Array of items!'); + throw new NodeOperationError(this.getNode(), 'No data got returned. Always return an Array of items!'); } if (!Array.isArray(items)) { - throw new Error('Always an Array of items has to be returned!'); + throw new NodeOperationError(this.getNode(), 'Always an Array of items has to be returned!'); } for (const item of items) { if (item.json === undefined) { - throw new Error('All returned items have to contain a property named "json"!'); + throw new NodeOperationError(this.getNode(), 'All returned items have to contain a property named "json"!'); } if (typeof item.json !== 'object') { - throw new Error('The json-property has to be an object!'); + throw new NodeOperationError(this.getNode(), 'The json-property has to be an object!'); } if (item.binary !== undefined) { if (Array.isArray(item.binary) || typeof item.binary !== 'object') { - throw new Error('The binary-property has to be an object!'); + throw new NodeOperationError(this.getNode(), 'The binary-property has to be an object!'); } } } diff --git a/packages/nodes-base/nodes/FunctionItem.node.ts b/packages/nodes-base/nodes/FunctionItem.node.ts index e7032e460e..8af33afeb9 100644 --- a/packages/nodes-base/nodes/FunctionItem.node.ts +++ b/packages/nodes-base/nodes/FunctionItem.node.ts @@ -5,6 +5,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; const { NodeVM } = require('vm2'); @@ -95,17 +96,18 @@ export class FunctionItem implements INodeType { const functionCode = this.getNodeParameter('functionCode', itemIndex) as string; + let jsonData: IDataObject; try { // Execute the function code jsonData = await vm.run(`module.exports = async function() {${functionCode}}()`, __dirname); - } catch (e) { - return Promise.reject(e); + } catch (error) { + return Promise.reject(error); } // Do very basic validation of the data if (jsonData === undefined) { - throw new Error('No data got returned. Always an object has to be returned!'); + throw new NodeOperationError(this.getNode(), 'No data got returned. Always an object has to be returned!'); } const returnItem: INodeExecutionData = { diff --git a/packages/nodes-base/nodes/GetResponse/GenericFunctions.ts b/packages/nodes-base/nodes/GetResponse/GenericFunctions.ts index f07d130da4..a0c8119f36 100644 --- a/packages/nodes-base/nodes/GetResponse/GenericFunctions.ts +++ b/packages/nodes-base/nodes/GetResponse/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject + IDataObject, NodeApiError } from 'n8n-workflow'; export async function getresponseApiRequest(this: IWebhookFunctions | IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -43,11 +43,7 @@ export async function getresponseApiRequest(this: IWebhookFunctions | IHookFunct return await this.helpers.requestOAuth2.call(this, 'getResponseOAuth2Api', options); } } catch (error) { - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - throw new Error(`GetResponse error response [${error.statusCode}]: ${error.response.body.message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/GetResponse/GetResponseTrigger.node.ts b/packages/nodes-base/nodes/GetResponse/GetResponseTrigger.node.ts index 3fe031b530..8fe8eb9718 100644 --- a/packages/nodes-base/nodes/GetResponse/GetResponseTrigger.node.ts +++ b/packages/nodes-base/nodes/GetResponse/GetResponseTrigger.node.ts @@ -10,6 +10,8 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -174,7 +176,7 @@ export class GetResponseTrigger implements INodeType { if (data.url !== webhookUrl) { if (deleteCurrentSubscription === false) { - throw new Error(`The webhook (${data.url}) is active in the account. Delete it manually or set the parameter "Delete Current Subscription" to true, and the node will delete it for you.`); + throw new NodeApiError(this.getNode(), data, { message: `The webhook (${data.url}) is active in the account. Delete it manually or set the parameter "Delete Current Subscription" to true, and the node will delete it for you.` }); } } } catch (error) { @@ -206,7 +208,7 @@ export class GetResponseTrigger implements INodeType { async delete(this: IHookFunctions): Promise { try { await getresponseApiRequest.call(this, 'DELETE', '/accounts/callbacks'); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/Ghost/GenericFunctions.ts b/packages/nodes-base/nodes/Ghost/GenericFunctions.ts index 89664f576e..62c6155918 100644 --- a/packages/nodes-base/nodes/Ghost/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Ghost/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; import * as jwt from 'jsonwebtoken'; @@ -60,21 +60,7 @@ export async function ghostApiRequest(this: IHookFunctions | IExecuteFunctions | return await this.helpers.request!(options); } catch (error) { - let errorMessages; - - if (error.response && error.response.body && error.response.body.errors) { - - if (Array.isArray(error.response.body.errors)) { - - const errors = error.response.body.errors; - - errorMessages = errors.map((e: IDataObject) => e.message); - } - - throw new Error(`Ghost error response [${error.statusCode}]: ${errorMessages?.join('|')}`); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Ghost/Ghost.node.ts b/packages/nodes-base/nodes/Ghost/Ghost.node.ts index ab2a3ea22c..241ae39ec9 100644 --- a/packages/nodes-base/nodes/Ghost/Ghost.node.ts +++ b/packages/nodes-base/nodes/Ghost/Ghost.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -226,7 +227,7 @@ export class Ghost implements INodeType { } else { const mobileDoc = validateJSON(content); if (mobileDoc === undefined) { - throw new Error('Content must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Content must be a valid JSON'); } post.mobiledoc = content; } @@ -240,7 +241,7 @@ export class Ghost implements INodeType { } if (post.status === 'scheduled' && post.published_at === undefined) { - throw new Error('Published at must be define when status is scheduled'); + throw new NodeOperationError(this.getNode(), 'Published at must be define when status is scheduled'); } responseData = await ghostApiRequest.call(this, 'POST', '/admin/posts', { posts: [post] }, qs); @@ -320,7 +321,7 @@ export class Ghost implements INodeType { } else { const mobileDoc = validateJSON(updateFields.contentJson as string || undefined); if (mobileDoc === undefined) { - throw new Error('Content must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Content must be a valid JSON'); } post.mobiledoc = updateFields.contentJson; delete updateFields.contentJson; @@ -335,7 +336,7 @@ export class Ghost implements INodeType { } if (post.status === 'scheduled' && post.published_at === undefined) { - throw new Error('Published at must be define when status is scheduled'); + throw new NodeOperationError(this.getNode(), 'Published at must be define when status is scheduled'); } post.updated_at = posts[0].updated_at; diff --git a/packages/nodes-base/nodes/Github/GenericFunctions.ts b/packages/nodes-base/nodes/Github/GenericFunctions.ts index 10d9558b3e..4b1ec4d6c5 100644 --- a/packages/nodes-base/nodes/Github/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Github/GenericFunctions.ts @@ -6,7 +6,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; /** @@ -41,7 +41,7 @@ export async function githubApiRequest(this: IHookFunctions | IExecuteFunctions, if (authenticationMethod === 'accessToken') { const credentials = this.getCredentials('githubApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const baseUrl = credentials!.server || 'https://api.github.com'; @@ -58,18 +58,7 @@ export async function githubApiRequest(this: IHookFunctions | IExecuteFunctions, return await this.helpers.requestOAuth2.call(this, 'githubOAuth2Api', options); } } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The Github credentials are not valid!'); - } - - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - throw new Error(`Github error response [${error.statusCode}]: ${error.response.body.message}`); - } - - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } @@ -95,7 +84,7 @@ export async function getFileSha(this: IHookFunctions | IExecuteFunctions, owner const responseData = await githubApiRequest.call(this, 'GET', getEndpoint, getBody, {}); if (responseData.sha === undefined) { - throw new Error('Could not get the SHA of the file.'); + throw new NodeOperationError(this.getNode(), 'Could not get the SHA of the file.'); } return responseData.sha; } diff --git a/packages/nodes-base/nodes/Github/Github.node.ts b/packages/nodes-base/nodes/Github/Github.node.ts index 77a5d5d24c..c1b27ffa02 100644 --- a/packages/nodes-base/nodes/Github/Github.node.ts +++ b/packages/nodes-base/nodes/Github/Github.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -1804,13 +1805,13 @@ export class Github implements INodeType { const item = items[i]; 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; 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!`); } // Currently internally n8n uses base64 and also Github expects it base64 encoded. @@ -2125,7 +2126,7 @@ export class Github implements INodeType { } } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } if (returnAll === true) { diff --git a/packages/nodes-base/nodes/Github/GithubTrigger.node.ts b/packages/nodes-base/nodes/Github/GithubTrigger.node.ts index 3d38dc5a55..98388c45d1 100644 --- a/packages/nodes-base/nodes/Github/GithubTrigger.node.ts +++ b/packages/nodes-base/nodes/Github/GithubTrigger.node.ts @@ -8,6 +8,8 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -350,8 +352,8 @@ export class GithubTrigger implements INodeType { try { await githubApiRequest.call(this, 'GET', endpoint, {}); - } catch (e) { - if (e.message.includes('[404]:')) { + } catch (error) { + if (error.message.includes('[404]:')) { // Webhook does not exist delete webhookData.webhookId; delete webhookData.webhookEvents; @@ -360,7 +362,7 @@ export class GithubTrigger implements INodeType { } // Some error occured - throw e; + throw error; } // If it did not error then the webhook exists @@ -370,7 +372,7 @@ export class GithubTrigger implements INodeType { const webhookUrl = this.getNodeWebhookUrl('default') as string; if (webhookUrl.includes('//localhost')) { - throw new Error('The Webhook can not work on "localhost". Please, either setup n8n on a custom domain or start with "--tunnel"!'); + throw new NodeOperationError(this.getNode(), 'The Webhook can not work on "localhost". Please, either setup n8n on a custom domain or start with "--tunnel"!'); } const owner = this.getNodeParameter('owner') as string; @@ -396,8 +398,8 @@ export class GithubTrigger implements INodeType { let responseData; try { responseData = await githubApiRequest.call(this, 'POST', endpoint, body); - } catch (e) { - if (e.message.includes('[422]:')) { + } catch (error) { + if (error.message.includes('[422]:')) { // Webhook exists already // Get the data of the already registered webhook @@ -416,15 +418,15 @@ export class GithubTrigger implements INodeType { } } - throw new Error('A webhook with the identical URL probably exists already. Please delete it manually on Github!'); + throw new NodeOperationError(this.getNode(), 'A webhook with the identical URL probably exists already. Please delete it manually on Github!'); } - throw e; + throw error; } if (responseData.id === undefined || responseData.active !== true) { // Required data is missing so was not successful - throw new Error('Github webhook creation response did not contain the expected data.'); + throw new NodeApiError(this.getNode(), responseData, { message: 'Github webhook creation response did not contain the expected data.' }); } webhookData.webhookId = responseData.id as string; @@ -443,7 +445,7 @@ export class GithubTrigger implements INodeType { try { await githubApiRequest.call(this, 'DELETE', endpoint, body); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts b/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts index c25c051ef5..6f50af91ba 100644 --- a/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Gitlab/GenericFunctions.ts @@ -5,7 +5,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import { OptionsWithUri } from 'request'; @@ -41,7 +41,7 @@ export async function gitlabApiRequest(this: IHookFunctions | IExecuteFunctions, if (authenticationMethod === 'accessToken') { const credentials = this.getCredentials('gitlabApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } options.headers!['Private-Token'] = `${credentials.accessToken}`; @@ -52,7 +52,7 @@ export async function gitlabApiRequest(this: IHookFunctions | IExecuteFunctions, } else { const credentials = this.getCredentials('gitlabOAuth2Api'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } options.uri = `${(credentials.server as string).replace(/\/$/, '')}/api/v4${endpoint}`; @@ -60,18 +60,7 @@ export async function gitlabApiRequest(this: IHookFunctions | IExecuteFunctions, return await this.helpers.requestOAuth2!.call(this, 'gitlabOAuth2Api', options); } } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The GitLab credentials are not valid!'); - } - - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - throw new Error(`Gitlab error response [${error.statusCode}]: ${error.response.body.message}`); - } - - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts index 08ff633af6..4b0f10d9fe 100644 --- a/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts +++ b/packages/nodes-base/nodes/Gitlab/Gitlab.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -1103,17 +1104,17 @@ export class Gitlab implements INodeType { credentials = this.getCredentials('gitlabApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } } else { credentials = this.getCredentials('gitlabOAuth2Api'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } } } catch (error) { - throw new Error(error); + throw new NodeOperationError(this.getNode(), error); } // Operations which overwrite the returned data @@ -1350,7 +1351,7 @@ export class Gitlab implements INodeType { endpoint = `/users/${owner}/projects`; } } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } if (returnAll === true) { diff --git a/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts b/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts index 4a8688f6ee..7a8828b93e 100644 --- a/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts +++ b/packages/nodes-base/nodes/Gitlab/GitlabTrigger.node.ts @@ -8,6 +8,8 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -176,8 +178,8 @@ export class GitlabTrigger implements INodeType { try { await gitlabApiRequest.call(this, 'GET', endpoint, {}); - } catch (e) { - if (e.message.includes('[404]:')) { + } catch (error) { + if (error.message.includes('[404]:')) { // Webhook does not exist delete webhookData.webhookId; delete webhookData.webhookEvents; @@ -186,7 +188,7 @@ export class GitlabTrigger implements INodeType { } // Some error occured - throw e; + throw error; } // If it did not error then the webhook exists @@ -231,13 +233,13 @@ export class GitlabTrigger implements INodeType { let responseData; try { responseData = await gitlabApiRequest.call(this, 'POST', endpoint, body); - } catch (e) { - throw e; + } catch (error) { + throw new NodeApiError(this.getNode(), error); } if (responseData.id === undefined) { // Required data is missing so was not successful - throw new Error('GitLab webhook creation response did not contain the expected data.'); + throw new NodeApiError(this.getNode(), responseData, { message: 'GitLab webhook creation response did not contain the expected data.' }); } const webhookData = this.getWorkflowStaticData('node'); @@ -260,7 +262,7 @@ export class GitlabTrigger implements INodeType { try { await gitlabApiRequest.call(this, 'DELETE', endpoint, body); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/GoToWebinar/GenericFunctions.ts b/packages/nodes-base/nodes/GoToWebinar/GenericFunctions.ts index 6f35da1f8d..c28a0217c1 100644 --- a/packages/nodes-base/nodes/GoToWebinar/GenericFunctions.ts +++ b/packages/nodes-base/nodes/GoToWebinar/GenericFunctions.ts @@ -7,6 +7,7 @@ import { IDataObject, ILoadOptionsFunctions, INodePropertyOptions, + NodeApiError, } from 'n8n-workflow'; import { @@ -71,19 +72,7 @@ export async function goToWebinarApiRequest( // https://stackoverflow.com/questions/62190724/getting-gotowebinar-registrant return losslessJSON.parse(response, convertLosslessNumber); } catch (error) { - - if (error?.response?.body) { - let errorMessage; - const body = JSON.parse(error.response.body); - if (Array.isArray(body.validationErrorCodes)) { - errorMessage = (body.validationErrorCodes as IDataObject[]).map((e) => e.description).join('|'); - } else { - errorMessage = body.description; - } - throw new Error(`Go To Webinar error response [${error.statusCode}]: ${errorMessage}`); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/GoToWebinar/GoToWebinar.node.ts b/packages/nodes-base/nodes/GoToWebinar/GoToWebinar.node.ts index 3fda2cd72c..409ce68cca 100644 --- a/packages/nodes-base/nodes/GoToWebinar/GoToWebinar.node.ts +++ b/packages/nodes-base/nodes/GoToWebinar/GoToWebinar.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -662,7 +663,7 @@ export class GoToWebinar implements INodeType { Object.assign(body, 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 endpoint = `organizers/${organizerKey}/webinars/${webinarKey}`; diff --git a/packages/nodes-base/nodes/Google/Analytics/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Analytics/GenericFunctions.ts index ed2da34c56..14f4905d26 100644 --- a/packages/nodes-base/nodes/Google/Analytics/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Analytics/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, @@ -36,25 +36,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF return await this.helpers.requestOAuth2.call(this, 'googleAnalyticsOAuth2', options); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - let errorMessages; - - if (error.response.body.error.errors) { - // Try to return the error prettier - errorMessages = error.response.body.error.errors; - - errorMessages = errorMessages.map((errorItem: IDataObject) => errorItem.message); - - errorMessages = errorMessages.join('|'); - - } else if (error.response.body.error.message) { - errorMessages = error.response.body.error.message; - } - - throw new Error(`Google Analytics error response [${error.statusCode}]: ${errorMessages}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/Books/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Books/GenericFunctions.ts index ef3f6187e3..ac299b7261 100644 --- a/packages/nodes-base/nodes/Google/Books/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Books/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import * as moment from 'moment-timezone'; @@ -40,7 +40,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF const credentials = this.getCredentials('googleApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const { access_token } = await getAccessToken.call(this, credentials as IDataObject); @@ -53,24 +53,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF return await this.helpers.requestOAuth2.call(this, 'googleBooksOAuth2Api', options); } } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - let errors; - - if (error.response.body.error.errors) { - errors = error.response.body.error.errors; - - errors = errors.map((e: IDataObject) => e.message).join('|'); - - } else { - errors = error.response.body.error.message; - } - // Try to return the error prettier - throw new Error( - `Google Books error response [${error.statusCode}]: ${errors}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts index edc841710c..4b9eff2928 100644 --- a/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -33,17 +33,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'googleCalendarOAuth2Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - let errors = error.response.body.error.errors; - - errors = errors.map((e: IDataObject) => e.message); - // Try to return the error prettier - throw new Error( - `Google Calendar error response [${error.statusCode}]: ${errors.join('|')}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts b/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts index e66f718ecb..755094916b 100644 --- a/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts +++ b/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts @@ -9,6 +9,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -208,11 +210,7 @@ export class GoogleCalendar implements INodeType { ); if (responseData.calendars[calendarId].errors) { - let errors = responseData.calendars[calendarId].errors; - errors = errors.map((e: IDataObject) => e.reason); - throw new Error( - `Google Calendar error response: ${errors.join('|')}`, - ); + throw new NodeApiError(this.getNode(), responseData.calendars[calendarId]); } if (outputFormat === 'availability') { @@ -328,7 +326,7 @@ export class GoogleCalendar implements INodeType { additionalFields.repeatHowManyTimes && additionalFields.repeatUntil ) { - throw new Error( + throw new NodeOperationError(this.getNode(), `You can set either 'Repeat How Many Times' or 'Repeat Until' but not both`, ); } @@ -573,7 +571,7 @@ export class GoogleCalendar implements INodeType { body.recurrence = [`RRULE:${updateFields.rrule}`]; } else { if (updateFields.repeatHowManyTimes && updateFields.repeatUntil) { - throw new Error( + throw new NodeOperationError(this.getNode(), `You can set either 'Repeat How Many Times' or 'Repeat Until' but not both`, ); } diff --git a/packages/nodes-base/nodes/Google/CloudNaturalLanguage/GenericFunctions.ts b/packages/nodes-base/nodes/Google/CloudNaturalLanguage/GenericFunctions.ts index 02d521c9c4..b053381e9e 100644 --- a/packages/nodes-base/nodes/Google/CloudNaturalLanguage/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/CloudNaturalLanguage/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, @@ -36,24 +36,6 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF return await this.helpers.requestOAuth2.call(this, 'googleCloudNaturalLanguageOAuth2Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - let errorMessages; - - if (error.response.body.error.errors) { - // Try to return the error prettier - errorMessages = error.response.body.error.errors; - - errorMessages = errorMessages.map((errorItem: IDataObject) => errorItem.message); - - errorMessages = errorMessages.join('|'); - - } else if (error.response.body.error.message) { - errorMessages = error.response.body.error.message; - } - - throw new Error(`Google Cloud Natural Language error response [${error.statusCode}]: ${errorMessages}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/Contacts/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Contacts/GenericFunctions.ts index 1493adeb24..a15861b1d6 100644 --- a/packages/nodes-base/nodes/Google/Contacts/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Contacts/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -34,26 +34,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'googleContactsOAuth2Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - let errors; - - if (error.response.body.error.errors) { - - errors = error.response.body.error.errors; - - errors = errors.map((e: IDataObject) => e.message).join('|'); - - } else { - errors = error.response.body.error.message; - } - - // Try to return the error prettier - throw new Error( - `Google Contacts error response [${error.statusCode}]: ${errors}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts index bf6d4a5c71..9d8a0ff13f 100644 --- a/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Drive/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import * as moment from 'moment-timezone'; @@ -39,7 +39,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF const credentials = this.getCredentials('googleApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const { access_token } = await getAccessToken.call(this, credentials as IDataObject); @@ -51,27 +51,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF return await this.helpers.requestOAuth2.call(this, 'googleDriveOAuth2Api', options); } } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - let errorMessages; - - if (error.response.body.error.errors) { - // Try to return the error prettier - errorMessages = error.response.body.error.errors; - - errorMessages = errorMessages.map((errorItem: IDataObject) => errorItem.message); - - errorMessages = errorMessages.join('|'); - - } else if (error.response.body.error.message) { - errorMessages = error.response.body.error.message; - } else if (error.response.body.error_description) { - errorMessages = error.response.body.error_description; - } - - throw new Error(`Google Drive error response [${error.statusCode}]: ${errorMessages}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts index 3e776a1fe7..220aee7598 100644 --- a/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts +++ b/packages/nodes-base/nodes/Google/Drive/GoogleDrive.node.ts @@ -8,6 +8,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -2146,13 +2147,13 @@ export class GoogleDrive implements INodeType { const item = items[i]; 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; 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!`); } if (item.binary[propertyNameUpload].mimeType) { @@ -2294,7 +2295,7 @@ export class GoogleDrive implements INodeType { returnData.push(response as IDataObject); } } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } } diff --git a/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.ts b/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.ts index e347222e7e..3e0f7a31f3 100644 --- a/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.ts +++ b/packages/nodes-base/nodes/Google/Drive/GoogleDriveTrigger.node.ts @@ -10,6 +10,7 @@ // INodeTypeDescription, // INodeType, // IWebhookResponseData, +// NodeOperationError, // } from 'n8n-workflow'; // import { getAuthenticationClient } from './GoogleApi'; @@ -75,8 +76,8 @@ // // try { // // await githubApiRequest.call(this, 'GET', endpoint, {}); -// // } catch (e) { -// // if (e.message.includes('[404]:')) { +// // } catch (error) { +// // if (error.message.includes('[404]:')) { // // // Webhook does not exist // // delete webhookData.webhookId; // // delete webhookData.webhookEvents; @@ -100,7 +101,7 @@ // const credentials = this.getCredentials('googleApi'); // if (credentials === undefined) { -// throw new Error('No credentials got returned!'); +// throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); // } // const scopes = [ @@ -165,9 +166,9 @@ // // let responseData; // // try { // // responseData = await githubApiRequest.call(this, 'POST', endpoint, body); -// // } catch (e) { -// // if (e.message.includes('[422]:')) { -// // throw new Error('A webhook with the identical URL exists already. Please delete it manually on Github!'); +// // } catch (error) { +// // if (error.message.includes('[422]:')) { +// // throw new NodeOperationError(this.getNode(), 'A webhook with the identical URL exists already. Please delete it manually on Github!'); // // } // // throw e; @@ -175,7 +176,7 @@ // // if (responseData.id === undefined || responseData.active !== true) { // // // Required data is missing so was not successful -// // throw new Error('Github webhook creation response did not contain the expected data.'); +// // throw new NodeOperationError(this.getNode(), 'Github webhook creation response did not contain the expected data.'); // // } // // const webhookData = this.getWorkflowStaticData('node'); @@ -192,7 +193,7 @@ // const credentials = this.getCredentials('googleApi'); // if (credentials === undefined) { -// throw new Error('No credentials got returned!'); +// throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); // } // const scopes = [ @@ -234,7 +235,7 @@ // // try { // // await githubApiRequest.call(this, 'DELETE', endpoint, body); -// // } catch (e) { +// // } catch (error) { // // return false; // // } diff --git a/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GenericFunctions.ts index f78bbaa824..cb58d5c496 100644 --- a/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Firebase/CloudFirestore/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri: string | null = null): Promise { // tslint:disable-line:no-any @@ -35,25 +35,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'googleFirebaseCloudFirestoreOAuth2Api', options); } catch (error) { - let errors; - - if (error.response && error.response.body) { - - if (Array.isArray(error.response.body)) { - - errors = error.response.body; - - errors = errors.map((e: { error: { message: string } }) => e.error.message).join('|'); - } else { - errors = error.response.body.error.message; - } - - // Try to return the error prettier - throw new Error( - `Google Firebase error response [${error.statusCode}]: ${errors}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/GenericFunctions.ts index 66a38a0666..acb50b22f4 100644 --- a/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, projectId: string, method: string, resource: string, body: any = {}, qs: IDataObject = {}, headers: IDataObject = {}, uri: string | null = null): Promise { // tslint:disable-line:no-any @@ -34,26 +34,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF return await this.helpers.requestOAuth2!.call(this, 'googleFirebaseRealtimeDatabaseOAuth2Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - let errors; - - if (error.response.body.error.errors) { - - errors = error.response.body.error.errors; - - errors = errors.map((e: IDataObject) => e.message).join('|'); - - } else { - errors = error.response.body.error.message; - } - - // Try to return the error prettier - throw new Error( - `Google Firebase error response [${error.statusCode}]: ${errors}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/RealtimeDatabase.node.ts b/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/RealtimeDatabase.node.ts index 38eaac6d0c..e28c9b8176 100644 --- a/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/RealtimeDatabase.node.ts +++ b/packages/nodes-base/nodes/Google/Firebase/RealtimeDatabase/RealtimeDatabase.node.ts @@ -9,6 +9,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -144,7 +146,7 @@ export class RealtimeDatabase implements INodeType { //https://firebase.google.com/docs/reference/rest/database if (['push', 'create', 'update'].includes(operation) && items.length === 1 && Object.keys(items[0].json).length === 0) { - throw new Error(`The ${operation} operation needs input data`); + throw new NodeOperationError(this.getNode(), `The ${operation} operation needs input data`); } for (let i = 0; i < length; i++) { @@ -185,7 +187,7 @@ export class RealtimeDatabase implements INodeType { if (responseData === null) { if (operation === 'get') { - throw new Error(`Google Firebase error response: Requested entity was not found.`); + throw new NodeApiError(this.getNode(), responseData, { message: `Requested entity was not found.` }); } else if (method === 'DELETE') { responseData = { success: true }; } diff --git a/packages/nodes-base/nodes/Google/GSuiteAdmin/GSuiteAdmin.node.ts b/packages/nodes-base/nodes/Google/GSuiteAdmin/GSuiteAdmin.node.ts index 1c4d83a693..b0ba6f6658 100644 --- a/packages/nodes-base/nodes/Google/GSuiteAdmin/GSuiteAdmin.node.ts +++ b/packages/nodes-base/nodes/Google/GSuiteAdmin/GSuiteAdmin.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -328,7 +329,7 @@ export class GSuiteAdmin implements INodeType { } if (qs.projection === 'custom' && qs.customFieldMask === undefined) { - throw new Error('When projection is set to custom, the custom schemas field must be defined'); + throw new NodeOperationError(this.getNode(), 'When projection is set to custom, the custom schemas field must be defined'); } responseData = await googleApiRequest.call( @@ -361,7 +362,7 @@ export class GSuiteAdmin implements INodeType { } if (qs.projection === 'custom' && qs.customFieldMask === undefined) { - throw new Error('When projection is set to custom, the custom schemas field must be defined'); + throw new NodeOperationError(this.getNode(), 'When projection is set to custom, the custom schemas field must be defined'); } if (returnAll) { diff --git a/packages/nodes-base/nodes/Google/GSuiteAdmin/GenericFunctions.ts b/packages/nodes-base/nodes/Google/GSuiteAdmin/GenericFunctions.ts index 81768cfba2..e95ecda637 100644 --- a/packages/nodes-base/nodes/Google/GSuiteAdmin/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/GSuiteAdmin/GenericFunctions.ts @@ -10,6 +10,7 @@ import { import { IDataObject, + NodeApiError, } from 'n8n-workflow'; export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -33,17 +34,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'gSuiteAdminOAuth2Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - let errors = error.response.body.error.errors; - - errors = errors.map((e: IDataObject) => e.message); - // Try to return the error prettier - throw new Error( - `G Suite Admin error response [${error.statusCode}]: ${errors.join('|')}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts index 29490df7a0..1507eb520c 100644 --- a/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts @@ -17,6 +17,8 @@ import { IBinaryKeyData, IDataObject, INodeExecutionData, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -58,7 +60,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF const credentials = this.getCredentials('googleApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const { access_token } = await getAccessToken.call(this, credentials as IDataObject); @@ -72,27 +74,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF } } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - let errorMessages; - - if (error.response.body.error.errors) { - // Try to return the error prettier - errorMessages = error.response.body.error.errors; - - errorMessages = errorMessages.map((errorItem: IDataObject) => errorItem.message); - - errorMessages = errorMessages.join('|'); - - } else if (error.response.body.error.message) { - errorMessages = error.response.body.error.message; - } else if (error.response.body.error_description) { - errorMessages = error.response.body.error_description; - } - - throw new Error(`Gmail error response [${error.statusCode}]: ${errorMessages}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/Sheet/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Sheet/GenericFunctions.ts index 80526a9ce1..91db4df182 100644 --- a/packages/nodes-base/nodes/Google/Sheet/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Sheet/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import * as moment from 'moment-timezone'; @@ -40,7 +40,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF const credentials = this.getCredentials('googleApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const { access_token } = await getAccessToken.call(this, credentials as IDataObject); @@ -53,11 +53,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF return await this.helpers.requestOAuth2.call(this, 'googleSheetsOAuth2Api', options); } } catch (error) { - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - throw new Error(`Google Sheet error response [${error.statusCode}]: ${error.response.body.message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/Sheet/GoogleSheet.ts b/packages/nodes-base/nodes/Google/Sheet/GoogleSheet.ts index 84453d7a77..479ab3cdd5 100644 --- a/packages/nodes-base/nodes/Google/Sheet/GoogleSheet.ts +++ b/packages/nodes-base/nodes/Google/Sheet/GoogleSheet.ts @@ -1,5 +1,5 @@ import { - IDataObject, + IDataObject, NodeOperationError, } from 'n8n-workflow'; import { @@ -283,7 +283,7 @@ export class GoogleSheet { const rangeEndSplit = rangeEnd.match(/([a-zA-Z]{1,10})([0-9]{0,10})/); if (rangeStartSplit === null || rangeStartSplit.length !== 3 || rangeEndSplit === null || rangeEndSplit.length !== 3) { - throw new Error(`The range "${range}" is not valid.`); + throw new NodeOperationError(this.executeFunctions.getNode(), `The range "${range}" is not valid.`); } const keyRowRange = `${sheet ? sheet + '!' : ''}${rangeStartSplit[1]}${dataStartRowIndex}:${rangeEndSplit[1]}${dataStartRowIndex}`; @@ -291,7 +291,7 @@ export class GoogleSheet { const sheetDatakeyRow = await this.getData(this.encodeRange(keyRowRange), valueRenderMode); if (sheetDatakeyRow === undefined) { - throw new Error('Could not retrieve the key row!'); + throw new NodeOperationError(this.executeFunctions.getNode(), 'Could not retrieve the key row!'); } const keyColumnOrder = sheetDatakeyRow[0]; @@ -299,7 +299,7 @@ export class GoogleSheet { const keyIndex = keyColumnOrder.indexOf(indexKey); if (keyIndex === -1) { - throw new Error(`Could not find column for key "${indexKey}"!`); + throw new NodeOperationError(this.executeFunctions.getNode(), `Could not find column for key "${indexKey}"!`); } const startRowIndex = rangeStartSplit[2] || ''; @@ -311,7 +311,7 @@ export class GoogleSheet { const sheetDataKeyColumn = await this.getData(this.encodeRange(keyColumnRange), valueRenderMode); if (sheetDataKeyColumn === undefined) { - throw new Error('Could not retrieve the key column!'); + throw new NodeOperationError(this.executeFunctions.getNode(), 'Could not retrieve the key column!'); } // TODO: The data till here can be cached optionally. Maybe add an option which can @@ -397,7 +397,7 @@ export class GoogleSheet { if (keyRowIndex < 0 || dataStartRowIndex < keyRowIndex || keyRowIndex >= inputData.length) { // The key row does not exist so it is not possible to look up the data - throw new Error(`The key row does not exist!`); + throw new NodeOperationError(this.executeFunctions.getNode(), `The key row does not exist!`); } // Create the keys array @@ -417,7 +417,7 @@ export class GoogleSheet { returnColumnIndex = keys.indexOf(lookupValue.lookupColumn); if (returnColumnIndex === -1) { - throw new Error(`The column "${lookupValue.lookupColumn}" could not be found!`); + throw new NodeOperationError(this.executeFunctions.getNode(), `The column "${lookupValue.lookupColumn}" could not be found!`); } // Loop over all the items and find the one with the matching value @@ -460,7 +460,7 @@ export class GoogleSheet { const keyColumnData = await this.getData(getRange, 'UNFORMATTED_VALUE'); if (keyColumnData === undefined) { - throw new Error('Could not retrieve the column data!'); + throw new NodeOperationError(this.executeFunctions.getNode(), 'Could not retrieve the column data!'); } const keyColumnOrder = keyColumnData[0]; diff --git a/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts b/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts index 58782e1e15..cb793ce0f4 100644 --- a/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts +++ b/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts @@ -10,6 +10,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -987,7 +988,7 @@ export class GoogleSheets implements INodeType { const responseData = await sheet.spreadsheetGetSheets(); if (responseData === undefined) { - throw new Error('No data got returned'); + throw new NodeOperationError(this.getNode(), 'No data got returned'); } const returnData: INodePropertyOptions[] = []; diff --git a/packages/nodes-base/nodes/Google/Slides/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Slides/GenericFunctions.ts index e25ec49e48..c580570032 100644 --- a/packages/nodes-base/nodes/Google/Slides/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Slides/GenericFunctions.ts @@ -9,6 +9,7 @@ import { import { IDataObject, + NodeApiError, } from 'n8n-workflow'; import * as moment from 'moment-timezone'; @@ -53,11 +54,7 @@ export async function googleApiRequest( return await this.helpers.requestOAuth2!.call(this, 'googleSlidesOAuth2Api', options); } } catch (error) { - - if (error?.response?.body?.message) { - throw new Error(`Google Slides error response [${error.statusCode}]: ${error.response.body.message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/Task/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Task/GenericFunctions.ts index e3dffba256..737ba1be09 100644 --- a/packages/nodes-base/nodes/Google/Task/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Task/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function googleApiRequest( @@ -46,17 +46,7 @@ export async function googleApiRequest( options, ); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - let errors = error.response.body.error.errors; - - errors = errors.map((e: IDataObject) => e.message); - // Try to return the error prettier - throw new Error( - `Google Tasks error response [${error.statusCode}]: ${errors.join('|')}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/Translate/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Translate/GenericFunctions.ts index 11ccf4d487..bd295e84c4 100644 --- a/packages/nodes-base/nodes/Google/Translate/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Translate/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import * as moment from 'moment-timezone'; @@ -40,7 +40,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF const credentials = this.getCredentials('googleApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const { access_token } = await getAccessToken.call(this, credentials as IDataObject); @@ -53,11 +53,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF return await this.helpers.requestOAuth2.call(this, 'googleTranslateOAuth2Api', options); } } catch (error) { - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - throw new Error(`Google Translate error response [${error.statusCode}]: ${error.response.body.message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/YouTube/GenericFunctions.ts b/packages/nodes-base/nodes/Google/YouTube/GenericFunctions.ts index c72b7fa8f9..518f8b6254 100644 --- a/packages/nodes-base/nodes/Google/YouTube/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/YouTube/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -33,28 +33,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'youTubeOAuth2Api', options); } catch (error) { - - let errors; - - if (error.response && error.response.body) { - - if (resource === '/upload/youtube/v3/videos') { - error.response.body = JSON.parse(error.response.body); - } - - if (error.response.body.error) { - - errors = error.response.body.error.errors; - - errors = errors.map((e: IDataObject) => e.message); - } - - // Try to return the error prettier - throw new Error( - `YouTube error response [${error.statusCode}]: ${errors.join('|')}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Google/YouTube/YouTube.node.ts b/packages/nodes-base/nodes/Google/YouTube/YouTube.node.ts index 156d90d935..9179277488 100644 --- a/packages/nodes-base/nodes/Google/YouTube/YouTube.node.ts +++ b/packages/nodes-base/nodes/Google/YouTube/YouTube.node.ts @@ -10,6 +10,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -414,11 +415,11 @@ export class YouTube implements INodeType { const item = items[i]; if (item.binary === undefined) { - throw new Error('No binary data exists on item!'); + throw new NodeOperationError(this.getNode(), 'No binary data exists on item!'); } if (item.binary[binaryProperty] === undefined) { - throw new Error(`No binary data property "${binaryProperty}" does not exists on item!`); + throw new NodeOperationError(this.getNode(), `No binary data property "${binaryProperty}" does not exists on item!`); } if (item.binary[binaryProperty].mimeType) { @@ -827,7 +828,7 @@ export class YouTube implements INodeType { } if (qs.relatedToVideoId && qs.forDeveloper !== undefined) { - throw new Error(`When using the parameter 'related to video' the parameter 'for developer' cannot be set`); + throw new NodeOperationError(this.getNode(), `When using the parameter 'related to video' the parameter 'for developer' cannot be set`); } if (returnAll) { @@ -901,11 +902,11 @@ export class YouTube implements INodeType { const item = items[i]; if (item.binary === undefined) { - throw new Error('No binary data exists on item!'); + throw new NodeOperationError(this.getNode(), 'No binary data exists on item!'); } if (item.binary[binaryProperty] === undefined) { - throw new Error(`No binary data property "${binaryProperty}" does not exists on item!`); + throw new NodeOperationError(this.getNode(), `No binary data property "${binaryProperty}" does not exists on item!`); } if (item.binary[binaryProperty].mimeType) { diff --git a/packages/nodes-base/nodes/Gotify/GenericFunctions.ts b/packages/nodes-base/nodes/Gotify/GenericFunctions.ts index ab694fe577..318f159ee4 100644 --- a/packages/nodes-base/nodes/Gotify/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Gotify/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function gotifyApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, uri?: string | undefined, option = {}): Promise { // tslint:disable-line:no-any @@ -35,15 +35,7 @@ export async function gotifyApiRequest(this: IExecuteFunctions | IExecuteSingleF //@ts-ignore return await this.helpers.request.call(this, options); } catch (error) { - - if (error.response && error.response.body && error.response.body.errorDescription) { - const message = error.response.body.errorDescription; - // Try to return the error prettier - throw new Error( - `Gotify error response [${error.statusCode}]: ${message}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/GraphQL/GraphQL.node.ts b/packages/nodes-base/nodes/GraphQL/GraphQL.node.ts index 167eda8e67..e18b2057d3 100644 --- a/packages/nodes-base/nodes/GraphQL/GraphQL.node.ts +++ b/packages/nodes-base/nodes/GraphQL/GraphQL.node.ts @@ -4,6 +4,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { OptionsWithUri } from 'request'; @@ -241,8 +243,8 @@ export class GraphQL implements INodeType { if (typeof requestOptions.body.variables === 'string') { try { requestOptions.body.variables = JSON.parse(requestOptions.body.variables || '{}'); - } catch (e) { - throw new Error('Using variables failed:\n' + requestOptions.body.variables + '\n\nWith error message:\n' + e); + } catch (error) { + throw new NodeOperationError(this.getNode(), 'Using variables failed:\n' + requestOptions.body.variables + '\n\nWith error message:\n' + error); } } if (requestOptions.body.operationName === '') { @@ -267,8 +269,8 @@ export class GraphQL implements INodeType { if (typeof response === 'string') { try { returnItems.push({ json: JSON.parse(response) }); - } catch (e) { - throw new Error('Response body is not valid JSON. Change "Response Format" to "String"'); + } catch (error) { + throw new NodeOperationError(this.getNode(), 'Response body is not valid JSON. Change "Response Format" to "String"'); } } else { returnItems.push({ json: response }); diff --git a/packages/nodes-base/nodes/Gumroad/GenericFunctions.ts b/packages/nodes-base/nodes/Gumroad/GenericFunctions.ts index de7afa5b12..48dc63528c 100644 --- a/packages/nodes-base/nodes/Gumroad/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Gumroad/GenericFunctions.ts @@ -6,12 +6,12 @@ import { ILoadOptionsFunctions, IWebhookFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function gumroadApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('gumroadApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } body = Object.assign({ access_token: credentials.accessToken }, body); @@ -30,6 +30,6 @@ export async function gumroadApiRequest(this: IHookFunctions | IExecuteFunctions try { return await this.helpers.request!(options); } catch (error) { - throw new Error('Gumroad Error: ' + error.message); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/HackerNews/GenericFunctions.ts b/packages/nodes-base/nodes/HackerNews/GenericFunctions.ts index 76fbdd3bb5..c94df5757d 100644 --- a/packages/nodes-base/nodes/HackerNews/GenericFunctions.ts +++ b/packages/nodes-base/nodes/HackerNews/GenericFunctions.ts @@ -6,13 +6,13 @@ import { import { IDataObject, ILoadOptionsFunctions, + NodeApiError, } from 'n8n-workflow'; import { OptionsWithUri, } from 'request'; - /** * Make an API request to HackerNews * @@ -33,13 +33,7 @@ export async function hackerNewsApiRequest(this: IHookFunctions | IExecuteFuncti try { return await this.helpers.request!(options); } catch (error) { - - if (error.response && error.response.body && error.response.body.error) { - // Try to return the error prettier - throw new Error(`Hacker News error response [${error.statusCode}]: ${error.response.body.error}`); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/HackerNews/HackerNews.node.ts b/packages/nodes-base/nodes/HackerNews/HackerNews.node.ts index c607b1b6b1..95fe4eeec1 100644 --- a/packages/nodes-base/nodes/HackerNews/HackerNews.node.ts +++ b/packages/nodes-base/nodes/HackerNews/HackerNews.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -328,7 +329,7 @@ export class HackerNews implements INodeType { endpoint = 'search?'; } else { - throw new Error(`The operation '${operation}' is unknown!`); + throw new NodeOperationError(this.getNode(), `The operation '${operation}' is unknown!`); } } else if (resource === 'article') { @@ -339,7 +340,7 @@ export class HackerNews implements INodeType { includeComments = additionalFields.includeComments as boolean; } else { - throw new Error(`The operation '${operation}' is unknown!`); + throw new NodeOperationError(this.getNode(), `The operation '${operation}' is unknown!`); } } else if (resource === 'user') { @@ -348,11 +349,11 @@ export class HackerNews implements INodeType { endpoint = `users/${this.getNodeParameter('username', i)}`; } else { - throw new Error(`The operation '${operation}' is unknown!`); + throw new NodeOperationError(this.getNode(), `The operation '${operation}' is unknown!`); } } else { - throw new Error(`The resource '${resource}' is unknown!`); + throw new NodeOperationError(this.getNode(), `The resource '${resource}' is unknown!`); } diff --git a/packages/nodes-base/nodes/Harvest/GenericFunctions.ts b/packages/nodes-base/nodes/Harvest/GenericFunctions.ts index fd5ed8178c..67da0a7eb6 100644 --- a/packages/nodes-base/nodes/Harvest/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Harvest/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function harvestApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, qs: IDataObject = {}, path: string, body: IDataObject = {}, option: IDataObject = {}, uri?: string): Promise { // tslint:disable-line:no-any @@ -38,7 +38,7 @@ export async function harvestApiRequest(this: IHookFunctions | IExecuteFunctions const credentials = this.getCredentials('harvestApi') as IDataObject; if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } //@ts-ignore @@ -49,18 +49,7 @@ export async function harvestApiRequest(this: IHookFunctions | IExecuteFunctions return await this.helpers.requestOAuth2!.call(this, 'harvestOAuth2Api', options); } } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The Harvest credentials are not valid!'); - } - - if (error.error && error.error.message) { - // Try to return the error prettier - throw new Error(`Harvest error response [${error.statusCode}]: ${error.error.message}`); - } - - // If that data does not exist for some reason return the actual error - throw new Error(`Harvest error response: ${error.message}`); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Harvest/Harvest.node.ts b/packages/nodes-base/nodes/Harvest/Harvest.node.ts index 5874355607..8540a83093 100644 --- a/packages/nodes-base/nodes/Harvest/Harvest.node.ts +++ b/packages/nodes-base/nodes/Harvest/Harvest.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -367,7 +368,7 @@ export class Harvest implements INodeType { returnData.push(responseData); } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else if (resource === 'client') { @@ -435,7 +436,7 @@ export class Harvest implements INodeType { const responseData = await harvestApiRequest.call(this, requestMethod, qs, endpoint); returnData.push(responseData); } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } } else if (resource === 'project') { if (operation === 'get') { @@ -506,7 +507,7 @@ export class Harvest implements INodeType { const responseData = await harvestApiRequest.call(this, requestMethod, qs, endpoint); returnData.push(responseData); } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } } else if (resource === 'user') { if (operation === 'get') { @@ -587,7 +588,7 @@ export class Harvest implements INodeType { const responseData = await harvestApiRequest.call(this, requestMethod, qs, endpoint); returnData.push(responseData); } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } } else if (resource === 'contact') { if (operation === 'get') { @@ -655,7 +656,7 @@ export class Harvest implements INodeType { const responseData = await harvestApiRequest.call(this, requestMethod, qs, endpoint); returnData.push(responseData); } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } } else if (resource === 'company') { if (operation === 'get') { @@ -670,7 +671,7 @@ export class Harvest implements INodeType { returnData.push(responseData); } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } } else if (resource === 'task') { if (operation === 'get') { @@ -737,7 +738,7 @@ export class Harvest implements INodeType { const responseData = await harvestApiRequest.call(this, requestMethod, qs, endpoint); returnData.push(responseData); } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } } else if (resource === 'invoice') { if (operation === 'get') { @@ -804,7 +805,7 @@ export class Harvest implements INodeType { const responseData = await harvestApiRequest.call(this, requestMethod, qs, endpoint); returnData.push(responseData); } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } } else if (resource === 'expense') { if (operation === 'get') { @@ -873,7 +874,7 @@ export class Harvest implements INodeType { const responseData = await harvestApiRequest.call(this, requestMethod, qs, endpoint); returnData.push(responseData); } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } } else if (resource === 'estimate') { if (operation === 'get') { @@ -940,10 +941,10 @@ export class Harvest implements INodeType { const responseData = await harvestApiRequest.call(this, requestMethod, qs, endpoint); returnData.push(responseData); } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } } diff --git a/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts b/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts index ffdde00d10..c48ea971c1 100644 --- a/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts +++ b/packages/nodes-base/nodes/HelpScout/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; import { @@ -38,17 +38,7 @@ export async function helpscoutApiRequest(this: IExecuteFunctions | IExecuteSing //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'helpScoutOAuth2Api', options); } catch (error) { - if (error.response && error.response.body - && error.response.body._embedded - && error.response.body._embedded.errors) { - // Try to return the error prettier - //@ts-ignore - throw new Error(`HelpScout error response [${error.statusCode}]: ${error.response.body.message} - ${error.response.body._embedded.errors.map(error => { - return `${error.path} ${error.message}`; - }).join('-')}`); - } - - throw new Error(`HelpScout error response [${error.statusCode}]: ${error.message}`); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts b/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts index 54f2495682..73dab6fef1 100644 --- a/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts +++ b/packages/nodes-base/nodes/HelpScout/HelpScout.node.ts @@ -10,6 +10,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -203,12 +204,12 @@ export class HelpScout implements INodeType { delete body.customerEmail; } if (body.customer === undefined) { - throw new Error('Either customer email or customer ID must be set'); + throw new NodeOperationError(this.getNode(), 'Either customer email or customer ID must be set'); } if (threads) { for (let i = 0; i < threads.length; i++) { if (threads[i].type === '' || threads[i].text === '') { - throw new Error('Chat Threads cannot be empty'); + throw new NodeOperationError(this.getNode(), 'Chat Threads cannot be empty'); } if (threads[i].type !== 'note') { threads[i].customer = body.customer; @@ -289,7 +290,7 @@ export class HelpScout implements INodeType { body.websites = websites; } if (Object.keys(body).length === 0) { - throw new Error('You have to set at least one field'); + throw new NodeOperationError(this.getNode(), 'You have to set at least one field'); } responseData = await helpscoutApiRequest.call(this, 'POST', '/v2/customers', body, qs, undefined, { resolveWithFullResponse: true }); const id = responseData.headers['resource-id']; @@ -335,7 +336,7 @@ export class HelpScout implements INodeType { body.age = body.age.toString(); } if (Object.keys(body).length === 0) { - throw new Error('You have to set at least one field'); + throw new NodeOperationError(this.getNode(), 'You have to set at least one field'); } responseData = await helpscoutApiRequest.call(this, 'PUT', `/v2/customers/${customerId}`, body, qs, undefined, { resolveWithFullResponse: true }); responseData = { success: true }; @@ -387,7 +388,7 @@ export class HelpScout implements INodeType { delete body.customerEmail; } if (body.customer === undefined) { - throw new Error('Either customer email or customer ID must be set'); + throw new NodeOperationError(this.getNode(), 'Either customer email or customer ID must be set'); } if (attachments) { if (attachments.attachmentsValues @@ -406,7 +407,7 @@ export class HelpScout implements INodeType { mimeType: binaryProperty.mimeType, }; } else { - throw new Error(`Binary property ${value.property} does not exist on input`); + throw new NodeOperationError(this.getNode(), `Binary property ${value.property} does not exist on input`); } }; body.attachments?.push.apply(body.attachments, (attachments.attachmentsBinary as IDataObject[]).map(mapFunction) as IAttachment[]); diff --git a/packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts b/packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts index 20a03de3f2..29bcbf8a19 100644 --- a/packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts +++ b/packages/nodes-base/nodes/HelpScout/HelpScoutTrigger.node.ts @@ -169,7 +169,7 @@ export class HelpScoutTrigger implements INodeType { const endpoint = `/v2/webhooks/${webhookData.webhookId}`; try { await helpscoutApiRequest.call(this, 'DELETE', endpoint); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/HtmlExtract/HtmlExtract.node.ts b/packages/nodes-base/nodes/HtmlExtract/HtmlExtract.node.ts index 4b70c5838f..0e5eb5e04c 100644 --- a/packages/nodes-base/nodes/HtmlExtract/HtmlExtract.node.ts +++ b/packages/nodes-base/nodes/HtmlExtract/HtmlExtract.node.ts @@ -5,6 +5,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; type Cheerio = ReturnType; @@ -228,15 +229,15 @@ export class HtmlExtract implements INodeType { let htmlArray: string[] | string = []; if (sourceData === 'json') { if (item.json[dataPropertyName] === undefined) { - throw new Error(`No property named "${dataPropertyName}" exists!`); + throw new NodeOperationError(this.getNode(), `No property named "${dataPropertyName}" exists!`); } htmlArray = item.json[dataPropertyName] as string; } else { if (item.binary === undefined) { - throw new Error(`No item does not contain binary data!`); + throw new NodeOperationError(this.getNode(), `No item does not contain binary data!`); } if (item.binary[dataPropertyName] === undefined) { - throw new Error(`No property named "${dataPropertyName}" exists!`); + throw new NodeOperationError(this.getNode(), `No property named "${dataPropertyName}" exists!`); } htmlArray = Buffer.from(item.binary[dataPropertyName].data, 'base64').toString('utf8'); } diff --git a/packages/nodes-base/nodes/HttpRequest.node.ts b/packages/nodes-base/nodes/HttpRequest.node.ts index dfd7dff69a..936ea877a7 100644 --- a/packages/nodes-base/nodes/HttpRequest.node.ts +++ b/packages/nodes-base/nodes/HttpRequest.node.ts @@ -8,6 +8,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { OptionsWithUri } from 'request'; @@ -713,19 +715,19 @@ export class HttpRequest implements INodeType { if (!contentTypesAllowed.includes(options.bodyContentType as string)) { // As n8n-workflow.NodeHelpers.getParamterResolveOrder can not be changed // easily to handle parameters in dot.notation simply error for now. - throw new Error('Sending binary data is only supported when option "Body Content Type" is set to "RAW/CUSTOM" or "FORM-DATA/MULTIPART"!'); + throw new NodeOperationError(this.getNode(), 'Sending binary data is only supported when option "Body Content Type" is set to "RAW/CUSTOM" or "FORM-DATA/MULTIPART"!'); } const item = items[itemIndex]; if (item.binary === undefined) { - throw new Error('No binary data exists on item!'); + throw new NodeOperationError(this.getNode(), 'No binary data exists on item!'); } if (options.bodyContentType === 'raw') { const binaryPropertyName = this.getNodeParameter('binaryPropertyName', itemIndex) as string; 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 binaryProperty = item.binary[binaryPropertyName] as IBinaryData; requestOptions.body = Buffer.from(binaryProperty.data, BINARY_ENCODING); @@ -742,11 +744,11 @@ export class HttpRequest implements INodeType { propertyName = propertyDataParts[0]; binaryPropertyName = propertyDataParts[1]; } else if (binaryPropertyNames.length > 1) { - throw new Error('If more than one property should be send it is needed to define the in the format: "sendKey1:binaryProperty1,sendKey2:binaryProperty2"'); + throw new NodeOperationError(this.getNode(), 'If more than one property should be send it is needed to define the in the format: "sendKey1:binaryProperty1,sendKey2:binaryProperty2"'); } 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 binaryProperty = item.binary[binaryPropertyName] as IBinaryData; @@ -778,8 +780,8 @@ export class HttpRequest implements INodeType { try { // @ts-ignore requestOptions[optionData.name] = JSON.parse(requestOptions[optionData.name]); - } catch (e) { - throw new Error(`The data in "${optionData.displayName}" is no valid JSON. Set Body Content Type to "RAW/Custom" for XML or other types of payloads`); + } catch (error) { + throw new NodeOperationError(this.getNode(), `The data in "${optionData.displayName}" is no valid JSON. Set Body Content Type to "RAW/Custom" for XML or other types of payloads`); } } } @@ -884,7 +886,7 @@ export class HttpRequest implements INodeType { if (response!.status !== 'fulfilled') { if (this.continueOnFail() !== true) { // throw error; - throw new Error(response!.reason); + throw new NodeApiError(this.getNode(), response); } else { // Return the actual reason as error returnItems.push( @@ -974,8 +976,8 @@ export class HttpRequest implements INodeType { if (responseFormat === 'json' && typeof returnItem.body === 'string') { try { returnItem.body = JSON.parse(returnItem.body); - } catch (e) { - throw new Error('Response body is not valid JSON. Change "Response Format" to "String"'); + } catch (error) { + throw new NodeOperationError(this.getNode(), 'Response body is not valid JSON. Change "Response Format" to "String"'); } } @@ -984,8 +986,8 @@ export class HttpRequest implements INodeType { if (responseFormat === 'json' && typeof response === 'string') { try { response = JSON.parse(response); - } catch (e) { - throw new Error('Response body is not valid JSON. Change "Response Format" to "String"'); + } catch (error) { + throw new NodeOperationError(this.getNode(), 'Response body is not valid JSON. Change "Response Format" to "String"'); } } diff --git a/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts b/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts index 5aec7790d8..dd98e667da 100644 --- a/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Hubspot/GenericFunctions.ts @@ -11,6 +11,7 @@ import { import { IDataObject, + NodeApiError, } from 'n8n-workflow'; export async function hubspotApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise { // tslint:disable-line:no-any @@ -47,27 +48,7 @@ export async function hubspotApiRequest(this: IHookFunctions | IExecuteFunctions return await this.helpers.requestOAuth2!.call(this, 'hubspotOAuth2Api', options, { tokenType: 'Bearer', includeCredentialsOnRefreshOnBody: true }); } } catch (error) { - let errorMessages; - - if (error.response && error.response.body) { - - if (error.response.body.message) { - - errorMessages = [error.response.body.message]; - - } else if (error.response.body.errors) { - // Try to return the error prettier - errorMessages = error.response.body.errors; - - if (errorMessages[0].message) { - // @ts-ignore - errorMessages = errorMessages.map(errorItem => errorItem.message); - } - } - throw new Error(`Hubspot error response [${error.statusCode}]: ${errorMessages.join('|')}`); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts b/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts index 8f29418dac..6281780e20 100644 --- a/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts +++ b/packages/nodes-base/nodes/Hubspot/HubspotTrigger.node.ts @@ -10,6 +10,8 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -280,7 +282,7 @@ export class HubspotTrigger implements INodeType { try { const { targetUrl } = await hubspotApiRequest.call(this, 'GET', `/webhooks/v3/${appId}/settings`, {}); if (targetUrl !== currentWebhookUrl) { - throw new Error(`The APP ID ${appId} already has a target url ${targetUrl}. Delete it or use another APP ID before executing the trigger. Due to Hubspot API limitations, you can have just one trigger per APP.`); + throw new NodeOperationError(this.getNode(), `The APP ID ${appId} already has a target url ${targetUrl}. Delete it or use another APP ID before executing the trigger. Due to Hubspot API limitations, you can have just one trigger per APP.`); } } catch (error) { if (error.statusCode === 404) { @@ -316,7 +318,7 @@ export class HubspotTrigger implements INodeType { endpoint = `/webhooks/v3/${appId}/subscriptions`; if (Array.isArray(events) && events.length === 0) { - throw new Error(`You must define at least one event`); + throw new NodeOperationError(this.getNode(), `You must define at least one event`); } for (const event of events) { @@ -344,7 +346,7 @@ export class HubspotTrigger implements INodeType { try { await hubspotApiRequest.call(this, 'DELETE', `/webhooks/v3/${appId}/settings`, {}); - } catch (e) { + } catch (error) { return false; } return true; @@ -357,7 +359,7 @@ export class HubspotTrigger implements INodeType { const credentials = this.getCredentials('hubspotDeveloperApi') as IDataObject; if (credentials === undefined) { - throw new Error('No credentials found!'); + throw new NodeOperationError(this.getNode(), 'No credentials found!'); } const req = this.getRequestObject(); diff --git a/packages/nodes-base/nodes/HumanticAI/GenericFunctions.ts b/packages/nodes-base/nodes/HumanticAI/GenericFunctions.ts index b2f785e13e..ac41db4ab7 100644 --- a/packages/nodes-base/nodes/HumanticAI/GenericFunctions.ts +++ b/packages/nodes-base/nodes/HumanticAI/GenericFunctions.ts @@ -10,14 +10,14 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function humanticAiApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, option: IDataObject = {}): Promise { // tslint:disable-line:no-any try { const credentials = this.getCredentials('humanticAiApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { headers: { @@ -40,20 +40,12 @@ export async function humanticAiApiRequest(this: IHookFunctions | IExecuteFuncti const response = await this.helpers.request!(options); if (response.data && response.data.status === 'error') { - throw new Error(`Humantic AI error response [400]: ${response.data.message}`); + throw new NodeApiError(this.getNode(), response.data); } return response; } catch (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(`Humantic AI error response [${error.statusCode}]: ${errorBody.message}`); - } - - // Expected error data did not get returned so throw the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/HumanticAI/HumanticAi.node.ts b/packages/nodes-base/nodes/HumanticAI/HumanticAi.node.ts index 334a53ddb0..d72598be51 100644 --- a/packages/nodes-base/nodes/HumanticAI/HumanticAi.node.ts +++ b/packages/nodes-base/nodes/HumanticAI/HumanticAi.node.ts @@ -10,6 +10,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -81,7 +82,7 @@ export class HumanticAi implements INodeType { const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; 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!'); } const item = items[i].binary as IBinaryKeyData; @@ -89,7 +90,7 @@ export class HumanticAi implements INodeType { const binaryData = item[binaryPropertyName] as IBinaryData; if (binaryData === 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!`); } responseData = await humanticAiApiRequest.call( @@ -141,7 +142,7 @@ export class HumanticAi implements INodeType { const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; 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!'); } const item = items[i].binary as IBinaryKeyData; @@ -149,7 +150,7 @@ export class HumanticAi implements INodeType { const binaryData = item[binaryPropertyName] as IBinaryData; if (binaryData === 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!`); } responseData = await humanticAiApiRequest.call( diff --git a/packages/nodes-base/nodes/Hunter/GenericFunctions.ts b/packages/nodes-base/nodes/Hunter/GenericFunctions.ts index 0cceacd771..a5385dd0b0 100644 --- a/packages/nodes-base/nodes/Hunter/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Hunter/GenericFunctions.ts @@ -5,12 +5,12 @@ import { IHookFunctions, ILoadOptionsFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function hunterApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('hunterApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } qs = Object.assign({ api_key: credentials.apiKey }, qs); let options: OptionsWithUri = { @@ -26,8 +26,8 @@ export async function hunterApiRequest(this: IHookFunctions | IExecuteFunctions } try { return await this.helpers.request!(options); - } catch (err) { - throw new Error(err); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/If.node.ts b/packages/nodes-base/nodes/If.node.ts index ba7e5e4441..278162d606 100644 --- a/packages/nodes-base/nodes/If.node.ts +++ b/packages/nodes-base/nodes/If.node.ts @@ -4,6 +4,7 @@ import { INodeParameters, INodeType, INodeTypeDescription, + NodeOperationError, NodeParameterValue, } from 'n8n-workflow'; @@ -326,7 +327,7 @@ export class If implements INodeType { }; // Converts the input data of a dateTime into a number for easy compare - function convertDateTime(value: NodeParameterValue): number { + const convertDateTime = (value: NodeParameterValue): number => { let returnValue: number | undefined = undefined; if (typeof value === 'string') { returnValue = new Date(value).getTime(); @@ -337,11 +338,11 @@ export class If implements INodeType { } if (returnValue === undefined || isNaN(returnValue)) { - throw new Error(`The value "${value}" is not a valid DateTime.`); + throw new NodeOperationError(this.getNode(), `The value "${value}" is not a valid DateTime.`); } return returnValue; - } + }; // The different dataTypes to check the values in const dataTypes = [ diff --git a/packages/nodes-base/nodes/Intercom/GenericFunctions.ts b/packages/nodes-base/nodes/Intercom/GenericFunctions.ts index 94a2f50f88..5f7602b56d 100644 --- a/packages/nodes-base/nodes/Intercom/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Intercom/GenericFunctions.ts @@ -8,13 +8,13 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function intercomApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('intercomApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const headerWithAuthentication = Object.assign({}, @@ -32,12 +32,7 @@ export async function intercomApiRequest(this: IHookFunctions | IExecuteFunction try { return await this.helpers.request!(options); } catch (error) { - const errorMessage = error.response.body.message || error.response.body.Message; - - if (errorMessage !== undefined) { - throw errorMessage; - } - throw error.response.body; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Intercom/Intercom.node.ts b/packages/nodes-base/nodes/Intercom/Intercom.node.ts index 181b37d96e..57903ca91d 100644 --- a/packages/nodes-base/nodes/Intercom/Intercom.node.ts +++ b/packages/nodes-base/nodes/Intercom/Intercom.node.ts @@ -8,6 +8,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { leadFields, @@ -101,8 +103,8 @@ export class Intercom implements INodeType { let companies, response; try { response = await intercomApiRequest.call(this, '/companies', 'GET'); - } catch (err) { - throw new Error(`Intercom Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } companies = response.companies; for (const company of companies) { @@ -214,8 +216,8 @@ export class Intercom implements INodeType { try { responseData = await intercomApiRequest.call(this, '/contacts', 'POST', body); - } catch (err) { - throw new Error(`Intercom Error: ${JSON.stringify(err)}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } if (operation === 'get') { @@ -237,8 +239,8 @@ export class Intercom implements INodeType { responseData = await intercomApiRequest.call(this, '/contacts', 'GET', {}, qs); responseData = responseData.contacts; } - } catch (err) { - throw new Error(`Intercom Error: ${JSON.stringify(err)}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } if (operation === 'getAll') { @@ -254,8 +256,8 @@ export class Intercom implements INodeType { responseData = await intercomApiRequest.call(this, '/contacts', 'GET', {}, qs); responseData = responseData.contacts; } - } catch (err) { - throw new Error(`Intercom Error: ${JSON.stringify(err)}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } if (operation === 'delete') { @@ -268,8 +270,8 @@ export class Intercom implements INodeType { qs.user_id = value; responseData = await intercomApiRequest.call(this, '/contacts', 'DELETE', {}, qs); } - } catch (err) { - throw new Error(`Intercom Error: ${JSON.stringify(err)}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } } @@ -378,8 +380,8 @@ export class Intercom implements INodeType { try { responseData = await intercomApiRequest.call(this, '/users', 'POST', body, qs); - } catch (err) { - throw new Error(`Intercom Error: ${JSON.stringify(err)}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } if (operation === 'get') { @@ -394,8 +396,8 @@ export class Intercom implements INodeType { } else { responseData = await intercomApiRequest.call(this, '/users', 'GET', {}, qs); } - } catch (err) { - throw new Error(`Intercom Error: ${JSON.stringify(err)}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } if (operation === 'getAll') { @@ -411,16 +413,16 @@ export class Intercom implements INodeType { responseData = await intercomApiRequest.call(this, '/users', 'GET', {}, qs); responseData = responseData.users; } - } catch (err) { - throw new Error(`Intercom Error: ${JSON.stringify(err)}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } if (operation === 'delete') { const id = this.getNodeParameter('id', i) as string; try { responseData = await intercomApiRequest.call(this, `/users/${id}`, 'DELETE'); - } catch (err) { - throw new Error(`Intercom Error: ${JSON.stringify(err)}`); + } catch (error) { + throw new NodeOperationError(this.getNode(), `Intercom Error: ${JSON.stringify(error)}`); } } } @@ -469,8 +471,8 @@ export class Intercom implements INodeType { } try { responseData = await intercomApiRequest.call(this, '/companies', 'POST', body, qs); - } catch (err) { - throw new Error(`Intercom Error: ${JSON.stringify(err)}`); + } catch (error) { + throw new NodeOperationError(this.getNode(), `Intercom Error: ${JSON.stringify(error)}`); } } if (operation === 'get') { @@ -488,8 +490,8 @@ export class Intercom implements INodeType { } else { responseData = await intercomApiRequest.call(this, '/companies', 'GET', {}, qs); } - } catch (err) { - throw new Error(`Intercom Error: ${JSON.stringify(err)}`); + } catch (error) { + throw new NodeOperationError(this.getNode(), `Intercom Error: ${JSON.stringify(error)}`); } } if (operation === 'getAll') { @@ -505,8 +507,8 @@ export class Intercom implements INodeType { responseData = await intercomApiRequest.call(this, '/companies', 'GET', {}, qs); responseData = responseData.companies; } - } catch (err) { - throw new Error(`Intercom Error: ${JSON.stringify(err)}`); + } catch (error) { + throw new NodeOperationError(this.getNode(), `Intercom Error: ${JSON.stringify(error)}`); } } if (operation === 'users') { @@ -522,8 +524,8 @@ export class Intercom implements INodeType { qs.type = 'users'; responseData = await intercomApiRequest.call(this, '/companies', 'GET', {}, qs); } - } catch (err) { - throw new Error(`Intercom Error: ${JSON.stringify(err)}`); + } catch (error) { + throw new NodeOperationError(this.getNode(), `Intercom Error: ${JSON.stringify(error)}`); } } } diff --git a/packages/nodes-base/nodes/Interval.node.ts b/packages/nodes-base/nodes/Interval.node.ts index 9abe96879a..cd7bcbd21e 100644 --- a/packages/nodes-base/nodes/Interval.node.ts +++ b/packages/nodes-base/nodes/Interval.node.ts @@ -3,6 +3,7 @@ import { INodeType, INodeTypeDescription, ITriggerResponse, + NodeOperationError, } from 'n8n-workflow'; @@ -62,7 +63,7 @@ export class Interval implements INodeType { const unit = this.getNodeParameter('unit') as string; if (interval <= 0) { - throw new Error('The interval has to be set to at least 1 or higher!'); + throw new NodeOperationError(this.getNode(), 'The interval has to be set to at least 1 or higher!'); } let intervalValue = interval; diff --git a/packages/nodes-base/nodes/InvoiceNinja/GenericFunctions.ts b/packages/nodes-base/nodes/InvoiceNinja/GenericFunctions.ts index 947be4d0c3..a43a86ecd1 100644 --- a/packages/nodes-base/nodes/InvoiceNinja/GenericFunctions.ts +++ b/packages/nodes-base/nodes/InvoiceNinja/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import { @@ -20,7 +20,7 @@ import { export async function invoiceNinjaApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('invoiceNinjaApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const baseUrl = credentials!.url || 'https://app.invoiceninja.com'; @@ -38,15 +38,7 @@ export async function invoiceNinjaApiRequest(this: IHookFunctions | IExecuteFunc try { return await this.helpers.request!(options); } catch (error) { - if (error.response && error.response.body && error.response.body.errors) { - // Try to return the error prettier - const errorMessages = Object.keys(error.response.body.errors).map(errorName => { - return (error.response.body.errors[errorName] as [string]).join(''); - }); - throw new Error(`Invoice Ninja error response [${error.statusCode}]: ${errorMessages.join(' | ')}`); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/InvoiceNinja/InvoiceNinjaTrigger.node.ts b/packages/nodes-base/nodes/InvoiceNinja/InvoiceNinjaTrigger.node.ts index b8fbe79a38..104c9d8949 100644 --- a/packages/nodes-base/nodes/InvoiceNinja/InvoiceNinjaTrigger.node.ts +++ b/packages/nodes-base/nodes/InvoiceNinja/InvoiceNinjaTrigger.node.ts @@ -112,7 +112,7 @@ export class InvoiceNinjaTrigger implements INodeType { try { await invoiceNinjaApiRequest.call(this, 'DELETE', endpoint); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/Iterable/GenericFunctions.ts b/packages/nodes-base/nodes/Iterable/GenericFunctions.ts index 35ec1e3420..e4d21f8adf 100644 --- a/packages/nodes-base/nodes/Iterable/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Iterable/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function iterableApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -38,16 +38,7 @@ export async function iterableApiRequest(this: IExecuteFunctions | IExecuteSingl return await this.helpers.request.call(this, options); } catch (error) { - if (error.response && error.response.body && error.response.body.msg) { - - const message = error.response.body.msg; - - // Try to return the error prettier - throw new Error( - `Iterable error response [${error.statusCode}]: ${message}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Iterable/Iterable.node.ts b/packages/nodes-base/nodes/Iterable/Iterable.node.ts index 46c1f47ad6..bde2a5acf2 100644 --- a/packages/nodes-base/nodes/Iterable/Iterable.node.ts +++ b/packages/nodes-base/nodes/Iterable/Iterable.node.ts @@ -9,6 +9,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -123,7 +125,7 @@ export class Iterable implements INodeType { const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; if (!additionalFields.email && !additionalFields.id) { - throw new Error('Either email or userId must be passed in to identify the user. Please add one of both via "Additional Fields". If both are passed in, email takes precedence.'); + throw new NodeOperationError(this.getNode(), 'Either email or userId must be passed in to identify the user. Please add one of both via "Additional Fields". If both are passed in, email takes precedence.'); } const body: IDataObject = { @@ -191,7 +193,7 @@ export class Iterable implements INodeType { if (this.continueOnFail() === false) { if (responseData.code !== 'Success') { - throw new Error( + throw new NodeOperationError(this.getNode(), `Iterable error response [400]: ${responseData.msg}`, ); } @@ -221,9 +223,7 @@ export class Iterable implements INodeType { if (this.continueOnFail() === false) { if (responseData.code !== 'Success') { - throw new Error( - `Iterable error response [400]: ${responseData.msg}`, - ); + throw new NodeApiError(this.getNode(), responseData); } } @@ -253,8 +253,8 @@ export class Iterable implements INodeType { if (this.continueOnFail() === false) { if (Object.keys(responseData).length === 0) { - throw new Error( - `Iterable error response [404]: User not found`, + throw new NodeApiError(this.getNode(), responseData, + { message: `User not found`, httpCode: '404' }, ); } } diff --git a/packages/nodes-base/nodes/Jira/GenericFunctions.ts b/packages/nodes-base/nodes/Jira/GenericFunctions.ts index 3ad028c680..8e384c1a76 100644 --- a/packages/nodes-base/nodes/Jira/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Jira/GenericFunctions.ts @@ -12,6 +12,8 @@ import { import { ICredentialDataDecryptedObject, IDataObject, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -27,7 +29,7 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut } if (jiraCredentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } if (jiraVersion === 'server') { @@ -67,22 +69,7 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut try { return await this.helpers.request!(options); } catch (error) { - - let errorMessage = error.message; - - if (error.response.body) { - if (error.response.body.errorMessages && error.response.body.errorMessages.length) { - errorMessage = JSON.stringify(error.response.body.errorMessages); - } else { - errorMessage = error.response.body.message || error.response.body.error || error.response.body.errors || error.message; - } - } - - if (typeof errorMessage !== 'string') { - errorMessage = JSON.stringify(errorMessage); - } - - throw new Error(`Jira error response [${error.statusCode}]: ${errorMessage}`); + throw new NodeApiError(this.getNode(), error); } } @@ -172,4 +159,4 @@ export const allEvents = [ 'worklog_created', 'worklog_updated', 'worklog_deleted', -]; \ No newline at end of file +]; diff --git a/packages/nodes-base/nodes/Jira/Jira.node.ts b/packages/nodes-base/nodes/Jira/Jira.node.ts index f11e6abc9a..2f73f3afa9 100644 --- a/packages/nodes-base/nodes/Jira/Jira.node.ts +++ b/packages/nodes-base/nodes/Jira/Jira.node.ts @@ -12,6 +12,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -457,7 +458,7 @@ export class Jira implements INodeType { } if (!additionalFields.parentIssueKey && subtaskIssues.includes(issueTypeId)) { - throw new Error('You must define a Parent Issue Key when Issue type is sub-task'); + throw new NodeOperationError(this.getNode(), 'You must define a Parent Issue Key when Issue type is sub-task'); } else if (additionalFields.parentIssueKey && subtaskIssues.includes(issueTypeId)) { @@ -531,7 +532,7 @@ export class Jira implements INodeType { } if (!updateFields.parentIssueKey && subtaskIssues.includes(updateFields.issueType)) { - throw new Error('You must define a Parent Issue Key when Issue type is sub-task'); + throw new NodeOperationError(this.getNode(), 'You must define a Parent Issue Key when Issue type is sub-task'); } else if (updateFields.parentIssueKey && subtaskIssues.includes(updateFields.issueType)) { @@ -737,7 +738,7 @@ export class Jira implements INodeType { const issueKey = this.getNodeParameter('issueKey', i) as string; 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!'); } const item = items[i].binary as IBinaryKeyData; @@ -745,7 +746,7 @@ export class Jira implements INodeType { const binaryData = item[binaryPropertyName] as IBinaryData; if (binaryData === 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!`); } responseData = await jiraSoftwareCloudApiRequest.call( @@ -860,7 +861,7 @@ export class Jira implements INodeType { const commentJson = this.getNodeParameter('commentJson', i) as string; const json = validateJSON(commentJson); if (json === '') { - throw new Error('Document Format must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Document Format must be a valid JSON'); } Object.assign(body, { body: json }); @@ -945,7 +946,7 @@ export class Jira implements INodeType { const commentJson = this.getNodeParameter('commentJson', i) as string; const json = validateJSON(commentJson); if (json === '') { - throw new Error('Document Format must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Document Format must be a valid JSON'); } Object.assign(body, { body: json }); diff --git a/packages/nodes-base/nodes/Jira/JiraTrigger.node.ts b/packages/nodes-base/nodes/Jira/JiraTrigger.node.ts index 68ada4ecf0..64ad396372 100644 --- a/packages/nodes-base/nodes/Jira/JiraTrigger.node.ts +++ b/packages/nodes-base/nodes/Jira/JiraTrigger.node.ts @@ -427,7 +427,7 @@ export class JiraTrigger implements INodeType { try { await jiraSoftwareCloudApiRequest.call(this, endpoint, 'DELETE', body); - } catch (e) { + } catch (error) { return false; } // Remove from the static workflow data so that it is clear diff --git a/packages/nodes-base/nodes/JotForm/GenericFunctions.ts b/packages/nodes-base/nodes/JotForm/GenericFunctions.ts index 85f6f0cee9..1f0bd6be97 100644 --- a/packages/nodes-base/nodes/JotForm/GenericFunctions.ts +++ b/packages/nodes-base/nodes/JotForm/GenericFunctions.ts @@ -6,12 +6,12 @@ import { ILoadOptionsFunctions, IWebhookFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function jotformApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('jotFormApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { headers: { @@ -32,6 +32,6 @@ export async function jotformApiRequest(this: IHookFunctions | IExecuteFunctions try { return await this.helpers.request!(options); } catch (error) { - throw new Error('JotForm Error: ' + error); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/JotForm/JotFormTrigger.node.ts b/packages/nodes-base/nodes/JotForm/JotFormTrigger.node.ts index c379ce3254..b118379a04 100644 --- a/packages/nodes-base/nodes/JotForm/JotFormTrigger.node.ts +++ b/packages/nodes-base/nodes/JotForm/JotFormTrigger.node.ts @@ -123,7 +123,7 @@ export class JotFormTrigger implements INodeType { const webhookIds = Object.keys(responseData.content); webhookData.webhookId = webhookIds[webhookUrls.indexOf(webhookUrl)]; - } catch (e) { + } catch (error) { return false; } return true; diff --git a/packages/nodes-base/nodes/Kafka/Kafka.node.ts b/packages/nodes-base/nodes/Kafka/Kafka.node.ts index 2b7876a672..6270da679a 100644 --- a/packages/nodes-base/nodes/Kafka/Kafka.node.ts +++ b/packages/nodes-base/nodes/Kafka/Kafka.node.ts @@ -15,6 +15,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; export class Kafka implements INodeType { @@ -195,7 +196,7 @@ export class Kafka implements INodeType { if (credentials.authentication === true) { if(!(credentials.username && credentials.password)) { - throw new Error('Username and password are required for authentication'); + throw new NodeOperationError(this.getNode(), 'Username and password are required for authentication'); } config.sasl = { username: credentials.username as string, @@ -230,7 +231,7 @@ export class Kafka implements INodeType { try { headers = JSON.parse(headers); } catch (exception) { - throw new Error('Headers must be a valid json'); + throw new NodeOperationError(this.getNode(), 'Headers must be a valid json'); } } else { const values = (this.getNodeParameter('headersUi', i) as IDataObject).headerValues as IDataObject[]; diff --git a/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts b/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts index 0c7e91abf4..32f3b7a9c9 100644 --- a/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts +++ b/packages/nodes-base/nodes/Kafka/KafkaTrigger.node.ts @@ -14,6 +14,7 @@ import { INodeType, INodeTypeDescription, ITriggerResponse, + NodeOperationError, } from 'n8n-workflow'; export class KafkaTrigger implements INodeType { @@ -125,7 +126,7 @@ export class KafkaTrigger implements INodeType { if (credentials.authentication === true) { if(!(credentials.username && credentials.password)) { - throw new Error('Username and password are required for authentication'); + throw new NodeOperationError(this.getNode(), 'Username and password are required for authentication'); } config.sasl = { username: credentials.username as string, @@ -156,7 +157,7 @@ export class KafkaTrigger implements INodeType { if (options.jsonParseMessage) { try { value = JSON.parse(value); - } catch (err) { } + } catch (error) { } } data.message = value; diff --git a/packages/nodes-base/nodes/Keap/GenericFunctions.ts b/packages/nodes-base/nodes/Keap/GenericFunctions.ts index c2a4c24970..63ad3ee0eb 100644 --- a/packages/nodes-base/nodes/Keap/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Keap/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject + IDataObject, NodeApiError } from 'n8n-workflow'; import { @@ -39,11 +39,7 @@ export async function keapApiRequest(this: IWebhookFunctions | IHookFunctions | //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'keapOAuth2Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - throw new Error(`Infusionsoft error response [${error.statusCode}]: ${error.response.body.message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Keap/Keap.node.ts b/packages/nodes-base/nodes/Keap/Keap.node.ts index 59692d0df5..d250f3a825 100644 --- a/packages/nodes-base/nodes/Keap/Keap.node.ts +++ b/packages/nodes-base/nodes/Keap/Keap.node.ts @@ -10,6 +10,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -708,7 +709,7 @@ export class Keap implements INodeType { && (attachmentsUi.attachmentsBinary as IDataObject).length) { 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!'); } for (const { property } of attachmentsUi.attachmentsBinary as IDataObject[]) { @@ -716,7 +717,7 @@ export class Keap implements INodeType { const item = items[i].binary as IBinaryKeyData; if (item[property as string] === undefined) { - throw new Error(`Binary data property "${property}" does not exists on item!`); + throw new NodeOperationError(this.getNode(), `Binary data property "${property}" does not exists on item!`); } attachments.push({ @@ -779,13 +780,13 @@ export class Keap implements INodeType { const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; 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!'); } const item = items[i].binary as IBinaryKeyData; if (item[binaryPropertyName as string] === 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!`); } body.file_data = item[binaryPropertyName as string].data; diff --git a/packages/nodes-base/nodes/Keap/KeapTrigger.node.ts b/packages/nodes-base/nodes/Keap/KeapTrigger.node.ts index fe608e58a1..17b037b104 100644 --- a/packages/nodes-base/nodes/Keap/KeapTrigger.node.ts +++ b/packages/nodes-base/nodes/Keap/KeapTrigger.node.ts @@ -140,7 +140,7 @@ export class KeapTrigger implements INodeType { try { await keapApiRequest.call(this, 'DELETE', `/hooks/${webhookData.webhookId}`); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/Lemlist/GenericFunctions.ts b/packages/nodes-base/nodes/Lemlist/GenericFunctions.ts index 1618f50b07..2d325b6e54 100644 --- a/packages/nodes-base/nodes/Lemlist/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Lemlist/GenericFunctions.ts @@ -6,6 +6,7 @@ import { import { IDataObject, ILoadOptionsFunctions, + NodeApiError, } from 'n8n-workflow'; import { @@ -57,12 +58,7 @@ export async function lemlistApiRequest( try { return await this.helpers.request!(options); } catch (error) { - - if (error?.response?.body) { - throw new Error(`Lemlist error response [${error.statusCode}]: ${error?.response?.body}`); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Line/GenericFunctions.ts b/packages/nodes-base/nodes/Line/GenericFunctions.ts index cc95fadd37..c8c0c392a3 100644 --- a/packages/nodes-base/nodes/Line/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Line/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function lineApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -36,15 +36,6 @@ export async function lineApiRequest(this: IExecuteFunctions | IExecuteSingleFun return await this.helpers.requestOAuth2.call(this, 'lineNotifyOAuth2Api', options, { tokenType: 'Bearer' }); } catch (error) { - - let errorMessage; - - if (error.response && error.response.body && error.response.body.message) { - - errorMessage = error.response.body.message; - - throw new Error(`Line error response [${error.statusCode}]: ${errorMessage}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Line/Line.node.ts b/packages/nodes-base/nodes/Line/Line.node.ts index 8c50baa354..b4681c3ec3 100644 --- a/packages/nodes-base/nodes/Line/Line.node.ts +++ b/packages/nodes-base/nodes/Line/Line.node.ts @@ -9,6 +9,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -108,11 +109,11 @@ export class Line implements INodeType { if (image && image.binaryData === true) { 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 if (items[i].binary[image.binaryProperty] === undefined) { - throw new Error(`No binary data property "${image.binaryProperty}" does not exists on item!`); + throw new NodeOperationError(this.getNode(), `No binary data property "${image.binaryProperty}" does not exists on item!`); } const binaryData = (items[i].binary as IBinaryKeyData)[image.binaryProperty as string]; diff --git a/packages/nodes-base/nodes/LingvaNex/GenericFunctions.ts b/packages/nodes-base/nodes/LingvaNex/GenericFunctions.ts index 3a0f43fbb9..58a549edda 100644 --- a/packages/nodes-base/nodes/LingvaNex/GenericFunctions.ts +++ b/packages/nodes-base/nodes/LingvaNex/GenericFunctions.ts @@ -10,14 +10,14 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function lingvaNexApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any try { const credentials = this.getCredentials('lingvaNexApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { headers: { @@ -35,20 +35,12 @@ export async function lingvaNexApiRequest(this: IHookFunctions | IExecuteFunctio const response = await this.helpers.request!(options); if (response.err !== null) { - throw new Error(`LingvaNex error response [400]: ${response.err}`); + throw new NodeApiError(this.getNode(), response); } return response; } catch (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(`LingvaNex error response [${error.statusCode}]: ${errorBody.message}`); - } - - // Expected error data did not get returned so throw the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/LinkedIn/GenericFunctions.ts b/packages/nodes-base/nodes/LinkedIn/GenericFunctions.ts index 09fe1e71f4..49879afaf2 100644 --- a/packages/nodes-base/nodes/LinkedIn/GenericFunctions.ts +++ b/packages/nodes-base/nodes/LinkedIn/GenericFunctions.ts @@ -7,6 +7,7 @@ import { IHookFunctions, ILoadOptionsFunctions, } from 'n8n-core'; +import { NodeApiError } from 'n8n-workflow'; export async function linkedInApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, binary?: boolean, headers?: object): Promise { // tslint:disable-line:no-any const options: OptionsWithUrl = { @@ -33,10 +34,7 @@ export async function linkedInApiRequest(this: IHookFunctions | IExecuteFunction try { return await this.helpers.requestOAuth2!.call(this, 'linkedInOAuth2Api', options, { tokenType: 'Bearer' }); } catch (error) { - if (error.respose && error.response.body && error.response.body.detail) { - throw new Error(`Linkedin Error response [${error.statusCode}]: ${error.response.body.detail}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts b/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts index 81f01e5450..2f7d3e0da9 100644 --- a/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts +++ b/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { linkedInApiRequest } from './GenericFunctions'; import { @@ -138,13 +139,13 @@ export class LinkedIn implements INodeType { const item = items[i]; 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; 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!`); } // Buffer binary data diff --git a/packages/nodes-base/nodes/MQTT/MqttTrigger.node.ts b/packages/nodes-base/nodes/MQTT/MqttTrigger.node.ts index 3a40941662..c99078e919 100644 --- a/packages/nodes-base/nodes/MQTT/MqttTrigger.node.ts +++ b/packages/nodes-base/nodes/MQTT/MqttTrigger.node.ts @@ -7,6 +7,7 @@ import { INodeType, INodeTypeDescription, ITriggerResponse, + NodeOperationError, } from 'n8n-workflow'; import * as mqtt from 'mqtt'; @@ -75,7 +76,7 @@ export class MqttTrigger implements INodeType { const credentials = this.getCredentials('mqtt'); if (!credentials) { - throw new Error('Credentials are mandatory!'); + throw new NodeOperationError(this.getNode(), 'Credentials are mandatory!'); } const topics = (this.getNodeParameter('topics') as string).split(','); @@ -83,7 +84,7 @@ export class MqttTrigger implements INodeType { const options = this.getNodeParameter('options') as IDataObject; if (!topics) { - throw new Error('Topics are mandatory!'); + throw new NodeOperationError(this.getNode(), 'Topics are mandatory!'); } const protocol = credentials.protocol as string || 'mqtt'; @@ -120,7 +121,7 @@ export class MqttTrigger implements INodeType { if (options.jsonParseMessage) { try { message = JSON.parse(message.toString()); - } catch (err) { } + } catch (error) { } } result.message = message; diff --git a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts index c8c24cd57c..1ce5ae2338 100644 --- a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, qs: IDataObject = {} ,headers?: object): Promise { // tslint:disable-line:no-any @@ -38,13 +38,13 @@ export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctio const credentials = this.getCredentials('mailchimpApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } options.headers = Object.assign({}, headers, { Authorization: `apikey ${credentials.apiKey}` }); if (!(credentials.apiKey as string).includes('-')) { - throw new Error('The API key is not valid!'); + throw new NodeOperationError(this.getNode(), 'The API key is not valid!'); } const datacenter = (credentials.apiKey as string).split('-').pop(); @@ -61,10 +61,7 @@ export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctio return await this.helpers.requestOAuth2!.call(this, 'mailchimpOAuth2Api', options, { tokenType: 'Bearer' }); } } catch (error) { - if (error.respose && error.response.body && error.response.body.detail) { - throw new Error(`Mailchimp Error response [${error.statusCode}]: ${error.response.body.detail}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Mailchimp/MailchimpTrigger.node.ts b/packages/nodes-base/nodes/Mailchimp/MailchimpTrigger.node.ts index b0f25a882a..5784dd5937 100644 --- a/packages/nodes-base/nodes/Mailchimp/MailchimpTrigger.node.ts +++ b/packages/nodes-base/nodes/Mailchimp/MailchimpTrigger.node.ts @@ -10,6 +10,8 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { mailchimpApiRequest, @@ -175,8 +177,8 @@ export class MailchimpTrigger implements INodeType { try { response = await mailchimpApiRequest.call(this, '/lists', 'GET'); lists = response.lists; - } catch (err) { - throw new Error(`Mailchimp Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } for (const list of lists) { const listName = list.name; @@ -205,11 +207,11 @@ export class MailchimpTrigger implements INodeType { const endpoint = `/lists/${listId}/webhooks/${webhookData.webhookId}`; try { await mailchimpApiRequest.call(this, endpoint, 'GET'); - } catch (err) { - if (err.statusCode === 404) { + } catch (error) { + if (error.statusCode === 404) { return false; } - throw new Error(`Mailchimp Error: ${err}`); + throw new NodeApiError(this.getNode(), error); } return true; }, @@ -236,8 +238,8 @@ export class MailchimpTrigger implements INodeType { const endpoint = `/lists/${listId}/webhooks`; try { webhook = await mailchimpApiRequest.call(this, endpoint, 'POST', body); - } catch (e) { - throw e; + } catch (error) { + throw error; } if (webhook.id === undefined) { return false; @@ -256,7 +258,7 @@ export class MailchimpTrigger implements INodeType { const endpoint = `/lists/${listId}/webhooks/${webhookData.webhookId}`; try { await mailchimpApiRequest.call(this, endpoint, 'DELETE', {}); - } catch (e) { + } catch (error) { return false; } delete webhookData.webhookId; diff --git a/packages/nodes-base/nodes/MailerLite/GenericFunctions.ts b/packages/nodes-base/nodes/MailerLite/GenericFunctions.ts index 388a75c594..b1ad7a5248 100644 --- a/packages/nodes-base/nodes/MailerLite/GenericFunctions.ts +++ b/packages/nodes-base/nodes/MailerLite/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function mailerliteApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise { // tslint:disable-line:no-any @@ -34,16 +34,7 @@ export async function mailerliteApiRequest(this: IExecuteFunctions | IExecuteSin //@ts-ignore return await this.helpers.request.call(this, options); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - const message = error.response.body.error.message; - - // Try to return the error prettier - throw new Error( - `Mailer Lite error response [${error.statusCode}]: ${message}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/MailerLite/MailerLiteTrigger.node.ts b/packages/nodes-base/nodes/MailerLite/MailerLiteTrigger.node.ts index 998da6ce24..658e059af0 100644 --- a/packages/nodes-base/nodes/MailerLite/MailerLiteTrigger.node.ts +++ b/packages/nodes-base/nodes/MailerLite/MailerLiteTrigger.node.ts @@ -162,7 +162,7 @@ export class MailerLiteTrigger implements INodeType { try { await mailerliteApiRequest.call(this, 'DELETE', endpoint); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/Mailgun/Mailgun.node.ts b/packages/nodes-base/nodes/Mailgun/Mailgun.node.ts index 39eeffb8dd..ebd13dae9f 100644 --- a/packages/nodes-base/nodes/Mailgun/Mailgun.node.ts +++ b/packages/nodes-base/nodes/Mailgun/Mailgun.node.ts @@ -7,6 +7,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; @@ -127,7 +129,7 @@ export class Mailgun implements INodeType { const credentials = this.getCredentials('mailgunApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const formData: IDataObject = { @@ -182,7 +184,13 @@ export class Mailgun implements INodeType { 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); + } returnData.push({ json: responseData, diff --git a/packages/nodes-base/nodes/Mailjet/GenericFunctions.ts b/packages/nodes-base/nodes/Mailjet/GenericFunctions.ts index aecdd5148a..6f101a1619 100644 --- a/packages/nodes-base/nodes/Mailjet/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mailjet/GenericFunctions.ts @@ -4,7 +4,7 @@ import { IExecuteSingleFunctions, ILoadOptionsFunctions, } from 'n8n-core'; -import { IDataObject, IHookFunctions } from 'n8n-workflow'; +import { IDataObject, IHookFunctions, NodeApiError } from 'n8n-workflow'; export async function mailjetApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const emailApiCredentials = this.getCredentials('mailjetEmailApi'); @@ -35,10 +35,7 @@ export async function mailjetApiRequest(this: IExecuteFunctions | IExecuteSingle try { return await this.helpers.request!(options); } catch (error) { - if (error.response.body || error.response.body.ErrorMessage) { - throw new Error(`Mailjet Error: response [${error.statusCode}]: ${error.response.body.ErrorMessage}`); - } - throw new Error(error); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Mandrill/GenericFunctions.ts b/packages/nodes-base/nodes/Mandrill/GenericFunctions.ts index cf113c56c8..d7ae92907c 100644 --- a/packages/nodes-base/nodes/Mandrill/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mandrill/GenericFunctions.ts @@ -9,12 +9,13 @@ import { } from 'n8n-core'; import * as _ from 'lodash'; +import { NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function mandrillApiRequest(this: IExecuteFunctions | IHookFunctions | ILoadOptionsFunctions, resource: string, method: string, action: string, body: any = {}, headers?: object): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('mandrillApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const data = Object.assign({}, body, { key: credentials.apiKey }); @@ -33,19 +34,7 @@ export async function mandrillApiRequest(this: IExecuteFunctions | IHookFunction try { return await this.helpers.request!(options); } catch (error) { - const errorMessage = error.response.body.message || error.response.body.Message; - if (error.name === 'Invalid_Key') { - throw new Error('The provided API key is not a valid Mandrill API key'); - } else if (error.name === 'ValidationError') { - throw new Error('The parameters passed to the API call are invalid or not provided when required'); - } else if (error.name === 'GeneralError') { - throw new Error('An unexpected error occurred processing the request. Mandrill developers will be notified.'); - } - - if (errorMessage !== undefined) { - throw errorMessage; - } - throw error.response.body; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Mandrill/Mandrill.node.ts b/packages/nodes-base/nodes/Mandrill/Mandrill.node.ts index c61173d183..c3fe7d14d6 100644 --- a/packages/nodes-base/nodes/Mandrill/Mandrill.node.ts +++ b/packages/nodes-base/nodes/Mandrill/Mandrill.node.ts @@ -9,6 +9,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -708,8 +710,8 @@ export class Mandrill implements INodeType { let templates; try { templates = await mandrillApiRequest.call(this, '/templates', 'POST', '/list'); - } catch (err) { - throw new Error(`Mandrill Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } for (const template of templates) { const templateName = template.name; diff --git a/packages/nodes-base/nodes/Matrix/GenericFunctions.ts b/packages/nodes-base/nodes/Matrix/GenericFunctions.ts index 8c54818b25..bed9eff1b4 100644 --- a/packages/nodes-base/nodes/Matrix/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Matrix/GenericFunctions.ts @@ -2,7 +2,7 @@ import { OptionsWithUri, } from 'request'; -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import { BINARY_ENCODING, @@ -52,7 +52,7 @@ export async function matrixApiRequest(this: IExecuteFunctions | IExecuteSingleF const credentials = this.getCredentials('matrixApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } //@ts-ignore options.uri = `${credentials.homeserverUrl}/_matrix/${option.overridePrefix || 'client'}/r0${resource}`; @@ -65,18 +65,7 @@ export async function matrixApiRequest(this: IExecuteFunctions | IExecuteSingleF //@ts-ignore return options.overridePrefix === 'media' ? JSON.parse(response) : response; } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('Matrix credentials are not valid!'); - } - - if (error.response && error.response.body && error.response.body.error) { - // Try to return the error prettier - throw new Error(`Matrix error response [${error.statusCode}]: ${error.response.body.error}`); - } - - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } @@ -201,7 +190,7 @@ export async function handleMatrixCall(this: IExecuteFunctions | IExecuteSingleF if (item.binary === undefined //@ts-ignore || 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!`); } //@ts-ignore @@ -243,5 +232,5 @@ export async function handleMatrixCall(this: IExecuteFunctions | IExecuteSingleF } - throw new Error('Not implemented yet'); + throw new NodeOperationError(this.getNode(), 'Not implemented yet'); } diff --git a/packages/nodes-base/nodes/Matrix/Matrix.node.ts b/packages/nodes-base/nodes/Matrix/Matrix.node.ts index 5109adc2ea..e7e503d2da 100644 --- a/packages/nodes-base/nodes/Matrix/Matrix.node.ts +++ b/packages/nodes-base/nodes/Matrix/Matrix.node.ts @@ -130,7 +130,7 @@ export class Matrix implements INodeType { name: roomNameResponse.name, value: roomId, }); - } catch (e) { + } catch (error) { // TODO: Check, there is probably another way to get the name of this private-chats returnData.push({ name: `Unknown: ${roomId}`, diff --git a/packages/nodes-base/nodes/Mattermost/GenericFunctions.ts b/packages/nodes-base/nodes/Mattermost/GenericFunctions.ts index 735024e38a..eba84ade8e 100644 --- a/packages/nodes-base/nodes/Mattermost/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mattermost/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'request'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export interface IAttachment { @@ -34,7 +34,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa const credentials = this.getCredentials('mattermostApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } query = query || {}; @@ -54,20 +54,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa try { return await this.helpers.request!(options); } catch (error) { - - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The Mattermost credentials are not valid!'); - } - - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - const errorBody = error.response.body; - throw new Error(`Mattermost error response: ${errorBody.message}`); - } - - // Expected error data did not get returned so throw the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Mattermost/Mattermost.node.ts b/packages/nodes-base/nodes/Mattermost/Mattermost.node.ts index 5d6476a83b..3d06b20110 100644 --- a/packages/nodes-base/nodes/Mattermost/Mattermost.node.ts +++ b/packages/nodes-base/nodes/Mattermost/Mattermost.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -1803,7 +1804,7 @@ export class Mattermost implements INodeType { const responseData = await apiRequest.call(this, 'GET', endpoint, {}); if (responseData === undefined) { - throw new Error('No data got returned'); + throw new NodeOperationError(this.getNode(), 'No data got returned'); } const returnData: INodePropertyOptions[] = []; @@ -1837,7 +1838,7 @@ export class Mattermost implements INodeType { const responseData = await apiRequest.call(this, 'GET', endpoint, {}); if (responseData === undefined) { - throw new Error('No data got returned'); + throw new NodeOperationError(this.getNode(), 'No data got returned'); } const returnData: INodePropertyOptions[] = []; @@ -1876,7 +1877,7 @@ export class Mattermost implements INodeType { const responseData = await apiRequest.call(this, 'GET', endpoint, {}); if (responseData === undefined) { - throw new Error('No data got returned'); + throw new NodeOperationError(this.getNode(), 'No data got returned'); } const returnData: INodePropertyOptions[] = []; @@ -1908,7 +1909,7 @@ export class Mattermost implements INodeType { const responseData = await apiRequest.call(this, 'GET', endpoint, {}); if (responseData === undefined) { - throw new Error('No data got returned'); + throw new NodeOperationError(this.getNode(), 'No data got returned'); } const returnData: INodePropertyOptions[] = []; @@ -1942,7 +1943,7 @@ export class Mattermost implements INodeType { const credentials = this.getCredentials('mattermostApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let operation: string; @@ -2279,26 +2280,26 @@ export class Mattermost implements INodeType { if (additionalFields.inTeam !== undefined && !validRules.inTeam.includes(snakeCase(additionalFields.sort as string))) { - throw new Error(`When In Team is set the only valid values for sorting are ${validRules.inTeam.join(',')}`); + throw new NodeOperationError(this.getNode(), `When In Team is set the only valid values for sorting are ${validRules.inTeam.join(',')}`); } if (additionalFields.inChannel !== undefined && !validRules.inChannel.includes(snakeCase(additionalFields.sort as string))) { - throw new Error(`When In Channel is set the only valid values for sorting are ${validRules.inChannel.join(',')}`); + throw new NodeOperationError(this.getNode(), `When In Channel is set the only valid values for sorting are ${validRules.inChannel.join(',')}`); } if (additionalFields.inChannel !== undefined && additionalFields.inChannel === '' && additionalFields.sort !== 'username') { - throw new Error('When sort is different than username In Channel must be set'); + throw new NodeOperationError(this.getNode(), 'When sort is different than username In Channel must be set'); } if (additionalFields.inTeam !== undefined && additionalFields.inTeam === '' && additionalFields.sort !== 'username') { - throw new Error('When sort is different than username In Team must be set'); + throw new NodeOperationError(this.getNode(), 'When sort is different than username In Team must be set'); } } else { - throw new Error(`When sort is defined either 'in team' or 'in channel' must be defined`); + throw new NodeOperationError(this.getNode(), `When sort is defined either 'in team' or 'in channel' must be defined`); } } @@ -2359,7 +2360,7 @@ export class Mattermost implements INodeType { } } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } let responseData; diff --git a/packages/nodes-base/nodes/Mautic/GenericFunctions.ts b/packages/nodes-base/nodes/Mautic/GenericFunctions.ts index d36add52df..4136fc5956 100644 --- a/packages/nodes-base/nodes/Mautic/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mautic/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; interface OMauticErrorResponse { @@ -20,17 +20,6 @@ interface OMauticErrorResponse { }>; } -export function getErrors(error: OMauticErrorResponse): string { - const returnErrors: string[] = []; - - for (const errorItem of error.errors) { - returnErrors.push(errorItem.message); - } - - return returnErrors.join(', '); -} - - export async function mauticApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any const authenticationMethod = this.getNodeParameter('authentication', 0, 'credentials') as string; @@ -68,15 +57,12 @@ export async function mauticApiRequest(this: IHookFunctions | IExecuteFunctions if (returnData.errors) { // They seem to to sometimes return 200 status but still error. - throw new Error(getErrors(returnData)); + throw new NodeApiError(this.getNode(), returnData); } return returnData; } catch (error) { - if (error.response && error.response.body && error.response.body.errors) { - throw new Error('Mautic Error: ' + getErrors(error.response.body)); - } - throw new Error(`Mautic Error: ${error.message}`); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Mautic/Mautic.node.ts b/packages/nodes-base/nodes/Mautic/Mautic.node.ts index 53915367fd..d3f30c036f 100644 --- a/packages/nodes-base/nodes/Mautic/Mautic.node.ts +++ b/packages/nodes-base/nodes/Mautic/Mautic.node.ts @@ -9,10 +9,11 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { - getErrors, mauticApiRequest, mauticApiRequestAllItems, validateJSON, @@ -265,7 +266,7 @@ export class Mautic implements INodeType { qs.start = 0; responseData = await mauticApiRequest.call(this, 'GET', '/companies', {}, qs); if (responseData.errors) { - throw new Error(getErrors(responseData)); + throw new NodeApiError(this.getNode(), responseData); } responseData = responseData.companies; responseData = Object.values(responseData); @@ -306,7 +307,7 @@ export class Mautic implements INodeType { if (json !== undefined) { body = { ...json }; } else { - throw new Error('Invalid JSON'); + throw new NodeOperationError(this.getNode(), 'Invalid JSON'); } } if (additionalFields.ipAddress) { @@ -415,7 +416,7 @@ export class Mautic implements INodeType { if (json !== undefined) { body = { ...json }; } else { - throw new Error('Invalid JSON'); + throw new NodeOperationError(this.getNode(), 'Invalid JSON'); } } if (updateFields.ipAddress) { @@ -524,7 +525,7 @@ export class Mautic implements INodeType { qs.start = 0; responseData = await mauticApiRequest.call(this, 'GET', '/contacts', {}, qs); if (responseData.errors) { - throw new Error(getErrors(responseData)); + throw new NodeApiError(this.getNode(), responseData); } responseData = responseData.contacts; responseData = Object.values(responseData); diff --git a/packages/nodes-base/nodes/Mautic/MauticTrigger.node.ts b/packages/nodes-base/nodes/Mautic/MauticTrigger.node.ts index b49d7f8bc2..239350bc50 100644 --- a/packages/nodes-base/nodes/Mautic/MauticTrigger.node.ts +++ b/packages/nodes-base/nodes/Mautic/MauticTrigger.node.ts @@ -144,7 +144,7 @@ export class MauticTrigger implements INodeType { const endpoint = `/hooks/${webhookData.webhookId}`; try { await mauticApiRequest.call(this, 'GET', endpoint, {}); - } catch (e) { + } catch (error) { return false; } return true; diff --git a/packages/nodes-base/nodes/Medium/GenericFunctions.ts b/packages/nodes-base/nodes/Medium/GenericFunctions.ts index 181002836d..fe6b5358e2 100644 --- a/packages/nodes-base/nodes/Medium/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Medium/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function mediumApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise { // tslint:disable-line:no-any @@ -35,7 +35,7 @@ export async function mediumApiRequest(this: IHookFunctions | IExecuteFunctions const credentials = this.getCredentials('mediumApi'); 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}`; @@ -46,9 +46,6 @@ export async function mediumApiRequest(this: IHookFunctions | IExecuteFunctions return await this.helpers.requestOAuth2!.call(this, 'mediumOAuth2Api', options); } } catch (error) { - if (error.statusCode === 401) { - throw new Error('The Medium credentials are not valid!'); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Medium/Medium.node.ts b/packages/nodes-base/nodes/Medium/Medium.node.ts index d7fcf745a0..372852c216 100644 --- a/packages/nodes-base/nodes/Medium/Medium.node.ts +++ b/packages/nodes-base/nodes/Medium/Medium.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -476,13 +477,13 @@ export class Medium implements INodeType { bodyRequest.tags = tags.split(',').map(name => { const returnValue = name.trim(); if (returnValue.length > 25) { - throw new Error(`The tag "${returnValue}" is to long. Maximum lenght of a tag is 25 characters.`); + throw new NodeOperationError(this.getNode(), `The tag "${returnValue}" is to long. Maximum lenght of a tag is 25 characters.`); } return returnValue; }); if ((bodyRequest.tags as string[]).length > 5) { - throw new Error('To many tags got used. Maximum 5 can be set.'); + throw new NodeOperationError(this.getNode(), 'To many tags got used. Maximum 5 can be set.'); } } diff --git a/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts b/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts index ae2cbb80e6..3501cc9275 100644 --- a/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts +++ b/packages/nodes-base/nodes/MessageBird/GenericFunctions.ts @@ -8,7 +8,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; /** @@ -29,7 +29,7 @@ export async function messageBirdApiRequest( ): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('messageBirdApi'); if (credentials === undefined) { - throw new Error('No credentials returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials returned!'); } const options: OptionsWithUri = { @@ -49,18 +49,6 @@ export async function messageBirdApiRequest( } return await this.helpers.request(options); } catch (error) { - if (error.statusCode === 401) { - throw new Error('The Message Bird credentials are not valid!'); - } - - if (error.response && error.response.body && error.response.body.errors) { - // Try to return the error prettier - const errorMessage = error.response.body.errors.map((e: IDataObject) => e.description); - - throw new Error(`MessageBird Error response [${error.statusCode}]: ${errorMessage.join('|')}`); - } - - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts b/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts index ab1b642135..d61b7ccb33 100644 --- a/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts +++ b/packages/nodes-base/nodes/MessageBird/MessageBird.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -380,7 +381,7 @@ export class MessageBird implements INodeType { }); } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else if (resource === 'balance') { @@ -388,7 +389,7 @@ export class MessageBird implements INodeType { requestPath = '/balance'; } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } const responseData = await messageBirdApiRequest.call( diff --git a/packages/nodes-base/nodes/Microsoft/Excel/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/Excel/GenericFunctions.ts index 9119c4c005..8dc0e4ee69 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/GenericFunctions.ts @@ -5,7 +5,7 @@ import { ILoadOptionsFunctions, } from 'n8n-core'; import { - IDataObject + IDataObject, NodeApiError } from 'n8n-workflow'; export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -26,11 +26,7 @@ export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSing //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'microsoftExcelOAuth2Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.error && error.response.body.error.message) { - // Try to return the error prettier - throw new Error(`Microsoft error response [${error.statusCode}]: ${error.response.body.error.message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts b/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts index 9953484b03..02f43789e0 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/MicrosoftExcel.node.ts @@ -9,6 +9,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -297,7 +299,7 @@ export class MicrosoftExcel implements INodeType { columns = columns.map((column: IDataObject) => column.name); if (!columns.includes(lookupColumn)) { - throw new Error(`Column ${lookupColumn} does not exist on the table selected`); + throw new NodeApiError(this.getNode(), responseData, { message: `Column ${lookupColumn} does not exist on the table selected` }); } result.length = 0; @@ -398,7 +400,7 @@ export class MicrosoftExcel implements INodeType { const keyRow = this.getNodeParameter('keyRow', i) as number; const dataStartRow = this.getNodeParameter('dataStartRow', i) as number; if (responseData.values === null) { - throw new Error('Range did not return data'); + throw new NodeApiError(this.getNode(), responseData, { message: 'Range did not return data' }); } const keyValues = responseData.values[keyRow]; for (let i = dataStartRow; i < responseData.values.length; i++) { diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts index b8cdc89cac..cc7ad8cd83 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject + IDataObject, NodeApiError } from 'n8n-workflow'; export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}, option: IDataObject = { json: true }): Promise { // tslint:disable-line:no-any @@ -36,11 +36,7 @@ export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSing //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'microsoftOneDriveOAuth2Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.error && error.response.body.error.message) { - // Try to return the error prettier - throw new Error(`Microsoft OneDrive response [${error.statusCode}]: ${error.response.body.error.message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts index a7bacc29fa..e36076bd65 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts @@ -9,6 +9,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -114,7 +116,7 @@ export class MicrosoftOneDrive implements INodeType { const fileName = responseData.name; if (responseData.file === undefined) { - throw new Error('The ID you provided does not belong to a file.'); + throw new NodeApiError(this.getNode(), responseData, { message: 'The ID you provided does not belong to a file.' }); } let mimeType: string | undefined; @@ -181,11 +183,11 @@ export class MicrosoftOneDrive implements INodeType { const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; 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 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]; @@ -197,7 +199,7 @@ export class MicrosoftOneDrive implements INodeType { } else { const body = this.getNodeParameter('fileContent', i) as string; if (fileName === '') { - throw new Error('File name must be set!'); + throw new NodeOperationError(this.getNode(), 'File name must be set!'); } responseData = await microsoftApiRequest.call(this, 'PUT', `/drive/items/${parentId}:/${fileName}:/content`, body , {}, undefined, { 'Content-Type': 'text/plain' } ); returnData.push(responseData as IDataObject); diff --git a/packages/nodes-base/nodes/Microsoft/Outlook/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/Outlook/GenericFunctions.ts index 8646ae08f2..4486605212 100644 --- a/packages/nodes-base/nodes/Microsoft/Outlook/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Microsoft/Outlook/GenericFunctions.ts @@ -11,6 +11,7 @@ import { import { IDataObject, INodeExecutionData, + NodeApiError, } from 'n8n-workflow'; export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}, option: IDataObject = { json: true }): Promise { // tslint:disable-line:no-any @@ -45,11 +46,7 @@ export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSing //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'microsoftOutlookOAuth2Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.error && error.response.body.error.message) { - // Try to return the error prettier - throw new Error(`Microsoft error response [${error.statusCode}]: ${error.response.body.error.message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Microsoft/Outlook/MicrosoftOutlook.node.ts b/packages/nodes-base/nodes/Microsoft/Outlook/MicrosoftOutlook.node.ts index 0cbd784f52..1373ee6f41 100644 --- a/packages/nodes-base/nodes/Microsoft/Outlook/MicrosoftOutlook.node.ts +++ b/packages/nodes-base/nodes/Microsoft/Outlook/MicrosoftOutlook.node.ts @@ -10,6 +10,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -244,11 +246,11 @@ export class MicrosoftOutlook implements INodeType { const binaryPropertyName = attachment.binaryPropertyName as string; 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 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]; @@ -338,11 +340,11 @@ export class MicrosoftOutlook implements INodeType { const binaryPropertyName = attachment.binaryPropertyName as string; 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 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]; @@ -516,11 +518,11 @@ export class MicrosoftOutlook implements INodeType { const binaryPropertyName = attachment.binaryPropertyName as string; 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 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]; @@ -558,11 +560,11 @@ export class MicrosoftOutlook implements INodeType { const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; 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 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]; @@ -571,7 +573,7 @@ export class MicrosoftOutlook implements INodeType { const fileName = additionalFields.fileName === undefined ? binaryData.fileName : additionalFields.fileName; if (!fileName) { - throw new Error('File name is not set. It has either to be set via "Additional Fields" or has to be set on the binary property!'); + throw new NodeOperationError(this.getNode(), 'File name is not set. It has either to be set via "Additional Fields" or has to be set on the binary property!'); } // Check if the file is over 3MB big @@ -596,7 +598,7 @@ export class MicrosoftOutlook implements INodeType { const uploadUrl = responseData.uploadUrl; if (uploadUrl === undefined) { - throw new Error('Failed to get upload session'); + throw new NodeApiError(this.getNode(), responseData, { message: 'Failed to get upload session' }); } for (let bytesUploaded = 0; bytesUploaded < dataBuffer.length; bytesUploaded += chunkSize) { diff --git a/packages/nodes-base/nodes/Microsoft/Sql/MicrosoftSql.node.ts b/packages/nodes-base/nodes/Microsoft/Sql/MicrosoftSql.node.ts index 16fdcce1dd..facc0dd3de 100644 --- a/packages/nodes-base/nodes/Microsoft/Sql/MicrosoftSql.node.ts +++ b/packages/nodes-base/nodes/Microsoft/Sql/MicrosoftSql.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -215,7 +216,7 @@ export class MicrosoftSql implements INodeType { const credentials = this.getCredentials('microsoftSql'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const config = { @@ -387,14 +388,14 @@ export class MicrosoftSql implements INodeType { } as IDataObject); } else { await pool.close(); - throw new Error(`The operation "${operation}" is not supported!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not supported!`); } - } catch (err) { + } catch (error) { if (this.continueOnFail() === true) { returnItems = items; } else { await pool.close(); - throw err; + throw error; } } diff --git a/packages/nodes-base/nodes/Microsoft/Teams/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/Teams/GenericFunctions.ts index ff49f86ff7..1baab4f292 100644 --- a/packages/nodes-base/nodes/Microsoft/Teams/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Microsoft/Teams/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -30,11 +30,7 @@ export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSing //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'microsoftTeamsOAuth2Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.error && error.response.body.error.message) { - // Try to return the error prettier - throw new Error(`Microsoft error response [${error.statusCode}]: ${error.response.body.error.message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Mindee/GenericFunctions.ts b/packages/nodes-base/nodes/Mindee/GenericFunctions.ts index d7ce39d3f3..7c02343462 100644 --- a/packages/nodes-base/nodes/Mindee/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mindee/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function mindeeApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise { // tslint:disable-line:no-any @@ -47,17 +47,7 @@ export async function mindeeApiRequest(this: IExecuteFunctions | IExecuteSingleF //@ts-ignore return await this.helpers.request.call(this, options); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - let errors = error.response.body.error.errors; - - errors = errors.map((e: IDataObject) => e.message); - // Try to return the error prettier - throw new Error( - `Mindee error response [${error.statusCode}]: ${errors.join('|')}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Mindee/Mindee.node.ts b/packages/nodes-base/nodes/Mindee/Mindee.node.ts index 56dbf99045..be504e88c9 100644 --- a/packages/nodes-base/nodes/Mindee/Mindee.node.ts +++ b/packages/nodes-base/nodes/Mindee/Mindee.node.ts @@ -10,6 +10,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -133,7 +134,7 @@ export class Mindee implements INodeType { const rawData = this.getNodeParameter('rawData', i) as boolean; 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!'); } const item = items[i].binary as IBinaryKeyData; @@ -141,7 +142,7 @@ export class Mindee implements INodeType { const binaryData = item[binaryPropertyName] as IBinaryData; if (binaryData === 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!`); } responseData = await mindeeApiRequest.call( @@ -175,7 +176,7 @@ export class Mindee implements INodeType { const rawData = this.getNodeParameter('rawData', i) as boolean; 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!'); } const item = items[i].binary as IBinaryKeyData; @@ -183,7 +184,7 @@ export class Mindee implements INodeType { const binaryData = item[binaryPropertyName] as IBinaryData; if (binaryData === 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!`); } responseData = await mindeeApiRequest.call( diff --git a/packages/nodes-base/nodes/Mocean/GenericFunctions.ts b/packages/nodes-base/nodes/Mocean/GenericFunctions.ts index 60f5373778..e415afc0b8 100644 --- a/packages/nodes-base/nodes/Mocean/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mocean/GenericFunctions.ts @@ -4,7 +4,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; /** @@ -19,7 +19,7 @@ import { export async function moceanApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: IDataObject, query?: IDataObject): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('moceanApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } if (query === undefined) { @@ -47,22 +47,6 @@ export async function moceanApiRequest(this: IHookFunctions | IExecuteFunctions, try { return await this.helpers.request(options); } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('Authentication failed.'); - } - - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - let errorMessage = error.response.body.message; - if (error.response.body.more_info) { - errorMessage += `errorMessage (${error.response.body.more_info})`; - } - - throw new Error(`Mocean error response [${error.statusCode}]: ${errorMessage}`); - } - - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Mocean/Mocean.node.ts b/packages/nodes-base/nodes/Mocean/Mocean.node.ts index f3b575a61d..3c2e9facfd 100644 --- a/packages/nodes-base/nodes/Mocean/Mocean.node.ts +++ b/packages/nodes-base/nodes/Mocean/Mocean.node.ts @@ -4,6 +4,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import {moceanApiRequest} from './GenericFunctions'; @@ -220,7 +221,7 @@ export class Mocean implements INodeType { body['mocean-text'] = text; endpoint = '/rest/2/sms'; } else { - throw new Error(`Unknown resource ${resource}`); + throw new NodeOperationError(this.getNode(), `Unknown resource ${resource}`); } if (operation === 'send') { @@ -232,7 +233,7 @@ export class Mocean implements INodeType { } } else { - throw new Error(`Unknown operation ${operation}`); + throw new NodeOperationError(this.getNode(), `Unknown operation ${operation}`); } } diff --git a/packages/nodes-base/nodes/MondayCom/GenericFunctions.ts b/packages/nodes-base/nodes/MondayCom/GenericFunctions.ts index 030e4f2310..f835c856b3 100644 --- a/packages/nodes-base/nodes/MondayCom/GenericFunctions.ts +++ b/packages/nodes-base/nodes/MondayCom/GenericFunctions.ts @@ -10,7 +10,9 @@ import { import { IDataObject, IHookFunctions, - IWebhookFunctions + IWebhookFunctions, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -18,6 +20,7 @@ import { } from 'lodash'; export async function mondayComApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, body: any = {}, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + const authenticationMethod = this.getNodeParameter('authentication', 0) as string; const endpoint = 'https://api.monday.com/v2/'; @@ -44,11 +47,7 @@ export async function mondayComApiRequest(this: IExecuteFunctions | IWebhookFunc return await this.helpers.requestOAuth2!.call(this, 'mondayComOAuth2Api', options); } } catch (error) { - if (error.response) { - const errorMessage = error.response.body.error_message; - throw new Error(`Monday error response [${error.statusCode}]: ${errorMessage}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/MondayCom/MondayCom.node.ts b/packages/nodes-base/nodes/MondayCom/MondayCom.node.ts index 389e9b19c8..0fa5568888 100644 --- a/packages/nodes-base/nodes/MondayCom/MondayCom.node.ts +++ b/packages/nodes-base/nodes/MondayCom/MondayCom.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -387,8 +388,8 @@ export class MondayCom implements INodeType { if (additionalFields.defaults) { try { JSON.parse(additionalFields.defaults as string); - } catch (e) { - throw new Error('Defauls must be a valid JSON'); + } catch (error) { + throw new NodeOperationError(this.getNode(), 'Defauls must be a valid JSON'); } body.variables.defaults = JSON.stringify(JSON.parse(additionalFields.defaults as string)); } @@ -532,8 +533,8 @@ export class MondayCom implements INodeType { try { JSON.parse(value); - } catch (e) { - throw new Error('Custom Values must be a valid JSON'); + } catch (error) { + throw new NodeOperationError(this.getNode(), 'Custom Values must be a valid JSON'); } body.variables.value = JSON.stringify(JSON.parse(value)); @@ -560,8 +561,8 @@ export class MondayCom implements INodeType { try { JSON.parse(columnValues); - } catch (e) { - throw new Error('Custom Values must be a valid JSON'); + } catch (error) { + throw new NodeOperationError(this.getNode(), 'Custom Values must be a valid JSON'); } body.variables.columnValues = JSON.stringify(JSON.parse(columnValues)); @@ -591,8 +592,8 @@ export class MondayCom implements INodeType { if (additionalFields.columnValues) { try { JSON.parse(additionalFields.columnValues as string); - } catch (e) { - throw new Error('Custom Values must be a valid JSON'); + } catch (error) { + throw new NodeOperationError(this.getNode(), 'Custom Values must be a valid JSON'); } body.variables.columnValues = JSON.stringify(JSON.parse(additionalFields.columnValues as string)); } diff --git a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts index 30eefb02b1..ed42fb80af 100644 --- a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts +++ b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts @@ -6,7 +6,8 @@ import { IDataObject, INodeExecutionData, INodeType, - INodeTypeDescription + INodeTypeDescription, + NodeOperationError } from 'n8n-workflow'; import { @@ -29,6 +30,7 @@ export class MongoDb implements INodeType { async execute(this: IExecuteFunctions): Promise { const { database, connectionString } = validateAndResolveMongoCredentials( + this, this.getCredentials('mongoDb'), ); @@ -157,7 +159,7 @@ export class MongoDb implements INodeType { } returnItems = this.helpers.returnJsonArray(updateItems as IDataObject[]); } else { - throw new Error(`The operation "${operation}" is not supported!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not supported!`); } client.close(); diff --git a/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts b/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts index 87fefd69b3..42dc4efcb0 100644 --- a/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts +++ b/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts @@ -1,7 +1,9 @@ +import { IExecuteFunctions } from 'n8n-core'; import { ICredentialDataDecryptedObject, IDataObject, INodeExecutionData, + NodeOperationError, } from 'n8n-workflow'; import { IMongoCredentials, @@ -28,9 +30,11 @@ function buildParameterizedConnString( * Build mongoDb connection string and resolve database name. * If a connection string override value is provided, that will be used in place of individual args * + * @param {IExecuteFunctions} self * @param {ICredentialDataDecryptedObject} credentials raw/input MongoDB credentials to use */ -function buildMongoConnectionParams( +export function buildMongoConnectionParams( + self: IExecuteFunctions, credentials: IMongoCredentialsType, ): IMongoCredentials { const sanitizedDbName = @@ -47,9 +51,7 @@ function buildMongoConnectionParams( database: sanitizedDbName, }; } else { - throw new Error( - 'Cannot override credentials: valid MongoDB connection string not provided ', - ); + throw new NodeOperationError(self.getNode(), 'Cannot override credentials: valid MongoDB connection string not provided '); } } else { return { @@ -62,15 +64,18 @@ function buildMongoConnectionParams( /** * Verify credentials. If ok, build mongoDb connection string and resolve database name. * + * @param {IExecuteFunctions} self * @param {ICredentialDataDecryptedObject} credentials raw/input MongoDB credentials to use */ export function validateAndResolveMongoCredentials( + self: IExecuteFunctions, credentials?: ICredentialDataDecryptedObject, ): IMongoCredentials { if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(self.getNode(), 'No credentials got returned!'); } else { return buildMongoConnectionParams( + self, credentials as unknown as IMongoCredentialsType, ); } diff --git a/packages/nodes-base/nodes/MoveBinaryData.node.ts b/packages/nodes-base/nodes/MoveBinaryData.node.ts index 666f8c3790..79e74f8244 100644 --- a/packages/nodes-base/nodes/MoveBinaryData.node.ts +++ b/packages/nodes-base/nodes/MoveBinaryData.node.ts @@ -16,6 +16,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import * as iconv from 'iconv-lite'; @@ -486,7 +487,7 @@ export class MoveBinaryData implements INodeType { } } } else { - throw new Error(`The operation "${mode}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${mode}" is not known!`); } returnData.push(newItem); diff --git a/packages/nodes-base/nodes/Msg91/GenericFunctions.ts b/packages/nodes-base/nodes/Msg91/GenericFunctions.ts index 3514916b02..a6d2e5a217 100644 --- a/packages/nodes-base/nodes/Msg91/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Msg91/GenericFunctions.ts @@ -4,7 +4,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; /** @@ -19,7 +19,7 @@ import { export async function msg91ApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: IDataObject, query?: IDataObject): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('msg91Api'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } if (query === undefined) { @@ -39,22 +39,6 @@ export async function msg91ApiRequest(this: IHookFunctions | IExecuteFunctions, try { return await this.helpers.request(options); } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The MSG91 credentials are not valid!'); - } - - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - let errorMessage = `MSG91 error response [${error.statusCode}]: ${error.response.body.message}`; - if (error.response.body.more_info) { - errorMessage = `errorMessage (${error.response.body.more_info})`; - } - - throw new Error(errorMessage); - } - - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Msg91/Msg91.node.ts b/packages/nodes-base/nodes/Msg91/Msg91.node.ts index 016d511fcb..6dc6b14b0a 100644 --- a/packages/nodes-base/nodes/Msg91/Msg91.node.ts +++ b/packages/nodes-base/nodes/Msg91/Msg91.node.ts @@ -4,6 +4,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -167,10 +168,10 @@ export class Msg91 implements INodeType { qs.message = this.getNodeParameter('message', i) as string; } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } const responseData = await msg91ApiRequest.call(this, requestMethod, endpoint, body, qs); diff --git a/packages/nodes-base/nodes/MySql/MySql.node.ts b/packages/nodes-base/nodes/MySql/MySql.node.ts index 03549c9260..0e587ae4ad 100644 --- a/packages/nodes-base/nodes/MySql/MySql.node.ts +++ b/packages/nodes-base/nodes/MySql/MySql.node.ts @@ -4,6 +4,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; // @ts-ignore import * as mysql2 from 'mysql2/promise'; @@ -215,7 +216,7 @@ export class MySql implements INodeType { const credentials = this.getCredentials('mySql'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } // Destructuring SSL configuration @@ -314,7 +315,7 @@ export class MySql implements INodeType { } else { await connection.end(); - throw new Error(`The operation "${operation}" is not supported!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not supported!`); } await connection.end(); diff --git a/packages/nodes-base/nodes/Nasa/GenericFunctions.ts b/packages/nodes-base/nodes/Nasa/GenericFunctions.ts index 50331f8ad0..cc8764d1e1 100644 --- a/packages/nodes-base/nodes/Nasa/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Nasa/GenericFunctions.ts @@ -8,7 +8,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function nasaApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, qs: IDataObject, option: IDataObject = {}, uri?: string | undefined): Promise { // tslint:disable-line:no-any @@ -32,18 +32,7 @@ export async function nasaApiRequest(this: IHookFunctions | IExecuteFunctions, m return await this.helpers.request(options); } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The NASA credentials are not valid!'); - } - - if (error.response && error.response.body && error.response.body.msg) { - // Try to return the error prettier - throw new Error(`NASA error response [${error.statusCode}]: ${error.response.body.msg}`); - } - - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Nasa/Nasa.node.ts b/packages/nodes-base/nodes/Nasa/Nasa.node.ts index c6a4f2e569..5b523c5bc0 100644 --- a/packages/nodes-base/nodes/Nasa/Nasa.node.ts +++ b/packages/nodes-base/nodes/Nasa/Nasa.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -981,7 +982,7 @@ export class Nasa implements INodeType { endpoint = `/neo/rest/v1/neo/${asteroidId}`; } else { - throw new Error(`The operation '${operation}' is unknown!`); + throw new NodeOperationError(this.getNode(), `The operation '${operation}' is unknown!`); } } @@ -1000,7 +1001,7 @@ export class Nasa implements INodeType { endpoint = `/neo/rest/v1/neo/browse`; } else { - throw new Error(`The operation '${operation}' is unknown!`); + throw new NodeOperationError(this.getNode(), `The operation '${operation}' is unknown!`); } } diff --git a/packages/nodes-base/nodes/NextCloud/GenericFunctions.ts b/packages/nodes-base/nodes/NextCloud/GenericFunctions.ts index 68bc908f92..263e6dd38c 100644 --- a/packages/nodes-base/nodes/NextCloud/GenericFunctions.ts +++ b/packages/nodes-base/nodes/NextCloud/GenericFunctions.ts @@ -2,6 +2,7 @@ import { IExecuteFunctions, IHookFunctions, } from 'n8n-core'; +import { NodeApiError, NodeOperationError, } from 'n8n-workflow'; import { OptionsWithUri, @@ -39,7 +40,7 @@ export async function nextCloudApiRequest(this: IHookFunctions | IExecuteFunctio if (authenticationMethod === 'accessToken') { const credentials = this.getCredentials('nextCloudApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } options.auth = { @@ -56,7 +57,7 @@ export async function nextCloudApiRequest(this: IHookFunctions | IExecuteFunctio } else { const credentials = this.getCredentials('nextCloudOAuth2Api'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } options.uri = `${credentials.webDavUrl}/${encodeURI(endpoint)}`; @@ -68,6 +69,6 @@ export async function nextCloudApiRequest(this: IHookFunctions | IExecuteFunctio return await this.helpers.requestOAuth2!.call(this, 'nextCloudOAuth2Api', options); } } catch (error) { - throw new Error(`NextCloud Error. Status Code: ${error.statusCode}. Message: ${error.message}`); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/NextCloud/NextCloud.node.ts b/packages/nodes-base/nodes/NextCloud/NextCloud.node.ts index dd65e4d86b..55a7025d9f 100644 --- a/packages/nodes-base/nodes/NextCloud/NextCloud.node.ts +++ b/packages/nodes-base/nodes/NextCloud/NextCloud.node.ts @@ -8,6 +8,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -591,7 +592,7 @@ export class NextCloud implements INodeType { } if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const resource = this.getNodeParameter('resource', 0) as string; @@ -627,14 +628,14 @@ export class NextCloud implements INodeType { const item = items[i]; 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; 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); @@ -719,7 +720,7 @@ export class NextCloud implements INodeType { } } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } // Make sure that the webdav URL does never have a trailing slash because diff --git a/packages/nodes-base/nodes/OpenThesaurus/GenericFunctions.ts b/packages/nodes-base/nodes/OpenThesaurus/GenericFunctions.ts index 5f50af03ef..ffed2ecc22 100644 --- a/packages/nodes-base/nodes/OpenThesaurus/GenericFunctions.ts +++ b/packages/nodes-base/nodes/OpenThesaurus/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function openThesaurusApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -31,14 +31,6 @@ export async function openThesaurusApiRequest(this: IHookFunctions | IExecuteFun return await this.helpers.request!(options); } catch (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(`OpenThesaurus error response [${error.statusCode}]: ${errorBody.message}`); - } - - // Expected error data did not get returned so throw the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/OpenWeatherMap.node.ts b/packages/nodes-base/nodes/OpenWeatherMap.node.ts index 7ad14ee88d..4aaa76a4bc 100644 --- a/packages/nodes-base/nodes/OpenWeatherMap.node.ts +++ b/packages/nodes-base/nodes/OpenWeatherMap.node.ts @@ -6,6 +6,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { OptionsWithUri } from 'request'; @@ -209,7 +211,7 @@ export class OpenWeatherMap implements INodeType { const credentials = this.getCredentials('openWeatherMapApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const operation = this.getNodeParameter('operation', 0) as string; @@ -239,7 +241,7 @@ export class OpenWeatherMap implements INodeType { } else if (locationSelection === 'zipCode') { qs.zip = this.getNodeParameter('zipCode', i) as string; } else { - throw new Error(`The locationSelection "${locationSelection}" is not known!`); + throw new NodeOperationError(this.getNode(), `The locationSelection "${locationSelection}" is not known!`); } // Get the language @@ -261,7 +263,7 @@ export class OpenWeatherMap implements INodeType { endpoint = 'forecast'; } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } const options: OptionsWithUri = { @@ -271,7 +273,13 @@ export class OpenWeatherMap implements INodeType { 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); + } + returnData.push(responseData as IDataObject); } diff --git a/packages/nodes-base/nodes/Orbit/GenericFunctions.ts b/packages/nodes-base/nodes/Orbit/GenericFunctions.ts index d1a4d48bb3..f0d4713461 100644 --- a/packages/nodes-base/nodes/Orbit/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Orbit/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import { @@ -21,7 +21,7 @@ export async function orbitApiRequest(this: IHookFunctions | IExecuteFunctions | try { const credentials = this.getCredentials('orbitApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { headers: { @@ -38,15 +38,7 @@ export async function orbitApiRequest(this: IHookFunctions | IExecuteFunctions | return await this.helpers.request!(options); } catch (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(`Orbit error response [${error.statusCode}]: ${errorBody.message}`); - } - - // Expected error data did not get returned so throw the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Oura/GenericFunctions.ts b/packages/nodes-base/nodes/Oura/GenericFunctions.ts index cd52b4bb4d..acf877b855 100644 --- a/packages/nodes-base/nodes/Oura/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Oura/GenericFunctions.ts @@ -11,6 +11,8 @@ import { import { IDataObject, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; export async function ouraApiRequest( @@ -25,7 +27,7 @@ export async function ouraApiRequest( const credentials = this.getCredentials('ouraApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { headers: { @@ -51,13 +53,6 @@ export async function ouraApiRequest( try { return await this.helpers.request!(options); } catch (error) { - - const errorMessage = error?.response?.body?.message; - - if (errorMessage) { - throw new Error(`Oura error response [${error.statusCode}]: ${errorMessage}`); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Paddle/GenericFunctions.ts b/packages/nodes-base/nodes/Paddle/GenericFunctions.ts index 72ae1d6a2b..0baed95f52 100644 --- a/packages/nodes-base/nodes/Paddle/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Paddle/GenericFunctions.ts @@ -11,14 +11,14 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function paddleApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('paddleApi'); if (credentials === undefined) { - throw new Error('Could not retrieve credentials!'); + throw new NodeOperationError(this.getNode(), 'Could not retrieve credentials!'); } const options: OptionsWithUri = { @@ -37,12 +37,12 @@ export async function paddleApiRequest(this: IHookFunctions | IExecuteFunctions const response = await this.helpers.request!(options); if (!response.success) { - throw new Error(`Code: ${response.error.code}. Message: ${response.error.message}`); + throw new NodeApiError(this.getNode(), response); } return response; } catch (error) { - throw new Error(`ERROR: Code: ${error.code}. Message: ${error.message}`); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Paddle/Paddle.node.ts b/packages/nodes-base/nodes/Paddle/Paddle.node.ts index a72526fe5c..6fdc69cf7b 100644 --- a/packages/nodes-base/nodes/Paddle/Paddle.node.ts +++ b/packages/nodes-base/nodes/Paddle/Paddle.node.ts @@ -9,6 +9,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -139,7 +141,7 @@ export class Paddle implements INodeType { // Alert user if there's no payments present to be loaded into payments property if (paymentResponse.response === undefined || paymentResponse.response.length === 0) { - throw Error('No payments on account.'); + throw new NodeApiError(this.getNode(), paymentResponse, { message: 'No payments on account.' }); } for (const payment of paymentResponse.response) { @@ -164,7 +166,7 @@ export class Paddle implements INodeType { // Alert user if there's no products present to be loaded into payments property if (products.length === 0) { - throw Error('No products on account.'); + throw new NodeOperationError(this.getNode(), 'No products on account.'); } for (const product of products) { @@ -200,7 +202,7 @@ export class Paddle implements INodeType { if (validateJSON(additionalFieldsJson) !== undefined) { Object.assign(body, JSON.parse(additionalFieldsJson)); } else { - throw new Error('Additional fields must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON'); } } @@ -285,7 +287,7 @@ export class Paddle implements INodeType { if (validateJSON(additionalFieldsJson) !== undefined) { Object.assign(body, JSON.parse(additionalFieldsJson)); } else { - throw new Error('Additional fields must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON'); } } @@ -358,7 +360,7 @@ export class Paddle implements INodeType { if (validateJSON(additionalFieldsJson) !== undefined) { Object.assign(body, JSON.parse(additionalFieldsJson)); } else { - throw new Error('Additional fields must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON'); } } @@ -475,7 +477,7 @@ export class Paddle implements INodeType { if (validateJSON(additionalFieldsJson) !== undefined) { Object.assign(body, JSON.parse(additionalFieldsJson)); } else { - throw new Error('Additional fields must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON'); } } diff --git a/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts b/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts index 2ca0ce6251..8b514fbad7 100644 --- a/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts +++ b/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts @@ -11,6 +11,8 @@ import { IDataObject, IHookFunctions, IWebhookFunctions, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -49,7 +51,7 @@ export async function pagerDutyApiRequest(this: IExecuteFunctions | IWebhookFunc const credentials = this.getCredentials('pagerDutyApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } options.headers!['Authorization'] = `Token token=${credentials.apiToken}`; @@ -59,14 +61,10 @@ export async function pagerDutyApiRequest(this: IExecuteFunctions | IWebhookFunc return await this.helpers.requestOAuth2!.call(this, 'pagerDutyOAuth2Api', options); } } catch (error) { - if (error.response && error.response.body && error.response.body.error && error.response.body.error.errors) { - // Try to return the error prettier - //@ts-ignore - throw new Error(`PagerDuty error response [${error.statusCode}]: ${error.response.body.error.errors.join(' | ')}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } + export async function pagerDutyApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any const returnData: IDataObject[] = []; diff --git a/packages/nodes-base/nodes/PayPal/GenericFunctions.ts b/packages/nodes-base/nodes/PayPal/GenericFunctions.ts index 25b7278ea6..f3d0aa42b9 100644 --- a/packages/nodes-base/nodes/PayPal/GenericFunctions.ts +++ b/packages/nodes-base/nodes/PayPal/GenericFunctions.ts @@ -10,10 +10,11 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function payPalApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('payPalApi'); const env = getEnvironment(credentials!.env as string); const tokenInfo = await getAccessToken.call(this); @@ -30,16 +31,7 @@ export async function payPalApiRequest(this: IHookFunctions | IExecuteFunctions try { return await this.helpers.request!(options); } catch (error) { - - if (error.response.body) { - let errorMessage = error.response.body.message; - if (error.response.body.details) { - errorMessage += ` - Details: ${JSON.stringify(error.response.body.details)}`; - } - throw new Error(errorMessage); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } @@ -54,7 +46,7 @@ function getEnvironment(env: string): string { async function getAccessToken(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('payPalApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const env = getEnvironment(credentials!.env as string); const data = Buffer.from(`${credentials!.clientId}:${credentials!.secret}`).toString(BINARY_ENCODING); @@ -72,12 +64,7 @@ async function getAccessToken(this: IHookFunctions | IExecuteFunctions | IExecut try { return await this.helpers.request!(options); } catch (error) { - const errorMessage = error.response.body.message || error.response.body.Message; - - if (errorMessage !== undefined) { - throw new Error(errorMessage); - } - throw new Error(error.response.body); + throw new NodeOperationError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/PayPal/PayPal.node.ts b/packages/nodes-base/nodes/PayPal/PayPal.node.ts index f22e1c93f3..7b12c8d835 100644 --- a/packages/nodes-base/nodes/PayPal/PayPal.node.ts +++ b/packages/nodes-base/nodes/PayPal/PayPal.node.ts @@ -6,6 +6,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { payoutFields, @@ -123,49 +124,34 @@ export class PayPal implements INodeType { }); body.items = payoutItems; } else { - throw new Error('You must have at least one item.'); + throw new NodeOperationError(this.getNode(), 'You must have at least one item.'); } } else { const itemsJson = validateJSON(this.getNodeParameter('itemsJson', i) as string); body.items = itemsJson; } - try { - responseData = await payPalApiRequest.call(this, '/payments/payouts', 'POST', body); - } catch (err) { - throw new Error(`PayPal Error: ${JSON.stringify(err)}`); - } + responseData = await payPalApiRequest.call(this, '/payments/payouts', 'POST', body); + } if (operation === 'get') { const payoutBatchId = this.getNodeParameter('payoutBatchId', i) as string; const returnAll = this.getNodeParameter('returnAll', 0) as boolean; - try { - if (returnAll === true) { - responseData = await payPalApiRequestAllItems.call(this, 'items', `/payments/payouts/${payoutBatchId}`, 'GET', {}, qs); - } else { - qs.page_size = this.getNodeParameter('limit', i) as number; - responseData = await payPalApiRequest.call(this, `/payments/payouts/${payoutBatchId}`, 'GET', {}, qs); - responseData = responseData.items; - } - } catch (err) { - throw new Error(`PayPal Error: ${JSON.stringify(err)}`); + if (returnAll === true) { + responseData = await payPalApiRequestAllItems.call(this, 'items', `/payments/payouts/${payoutBatchId}`, 'GET', {}, qs); + } else { + qs.page_size = this.getNodeParameter('limit', i) as number; + responseData = await payPalApiRequest.call(this, `/payments/payouts/${payoutBatchId}`, 'GET', {}, qs); + responseData = responseData.items; } } } else if (resource === 'payoutItem') { if (operation === 'get') { const payoutItemId = this.getNodeParameter('payoutItemId', i) as string; - try { - responseData = await payPalApiRequest.call(this,`/payments/payouts-item/${payoutItemId}`, 'GET', {}, qs); - } catch (err) { - throw new Error(`PayPal Error: ${JSON.stringify(err)}`); - } + responseData = await payPalApiRequest.call(this,`/payments/payouts-item/${payoutItemId}`, 'GET', {}, qs); } if (operation === 'cancel') { const payoutItemId = this.getNodeParameter('payoutItemId', i) as string; - try { - responseData = await payPalApiRequest.call(this,`/payments/payouts-item/${payoutItemId}/cancel`, 'POST', {}, qs); - } catch (err) { - throw new Error(`PayPal Error: ${JSON.stringify(err)}`); - } + responseData = await payPalApiRequest.call(this,`/payments/payouts-item/${payoutItemId}/cancel`, 'POST', {}, qs); } } if (Array.isArray(responseData)) { diff --git a/packages/nodes-base/nodes/PayPal/PayPalTrigger.node.ts b/packages/nodes-base/nodes/PayPal/PayPalTrigger.node.ts index 94b69ade48..b25ce6e1e0 100644 --- a/packages/nodes-base/nodes/PayPal/PayPalTrigger.node.ts +++ b/packages/nodes-base/nodes/PayPal/PayPalTrigger.node.ts @@ -10,6 +10,8 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { payPalApiRequest, @@ -76,8 +78,8 @@ export class PayPalTrigger implements INodeType { try { const endpoint = '/notifications/webhooks-event-types'; events = await payPalApiRequest.call(this, endpoint, 'GET'); - } catch (err) { - throw new Error(`PayPal Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } for (const event of events.event_types) { const eventName = upperFist(event.name); @@ -107,13 +109,13 @@ export class PayPalTrigger implements INodeType { const endpoint = `/notifications/webhooks/${webhookData.webhookId}`; try { await payPalApiRequest.call(this, endpoint, 'GET'); - } catch (err) { - if (err.response && err.response.name === 'INVALID_RESOURCE_ID') { + } catch (error) { + if (error.response && error.response.name === 'INVALID_RESOURCE_ID') { // Webhook does not exist delete webhookData.webhookId; return false; } - throw new Error(`PayPal Error: ${err}`); + throw new NodeApiError(this.getNode(), error); } return true; }, @@ -131,8 +133,8 @@ export class PayPalTrigger implements INodeType { const endpoint = '/notifications/webhooks'; try { webhook = await payPalApiRequest.call(this, endpoint, 'POST', body); - } catch (e) { - throw e; + } catch (error) { + throw error; } if (webhook.id === undefined) { @@ -149,7 +151,7 @@ export class PayPalTrigger implements INodeType { const endpoint = `/notifications/webhooks/${webhookData.webhookId}`; try { await payPalApiRequest.call(this, endpoint, 'DELETE', {}); - } catch (e) { + } catch (error) { return false; } delete webhookData.webhookId; @@ -183,8 +185,8 @@ export class PayPalTrigger implements INodeType { }; try { webhook = await payPalApiRequest.call(this, endpoint, 'POST', body); - } catch (e) { - throw e; + } catch (error) { + throw error; } if (webhook.verification_status !== 'SUCCESS') { return {}; diff --git a/packages/nodes-base/nodes/Peekalink/GenericFunctions.ts b/packages/nodes-base/nodes/Peekalink/GenericFunctions.ts index 8e1c339fd7..3ae4f2bdf9 100644 --- a/packages/nodes-base/nodes/Peekalink/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Peekalink/GenericFunctions.ts @@ -10,14 +10,14 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function peekalinkApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any try { const credentials = this.getCredentials('peekalinkApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { headers: { @@ -34,14 +34,6 @@ export async function peekalinkApiRequest(this: IHookFunctions | IExecuteFunctio return await this.helpers.request!(options); } catch (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(`Peekalink error response [${error.statusCode}]: ${errorBody.message}`); - } - - // Expected error data did not get returned so throw the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Phantombuster/GenericFunctions.ts b/packages/nodes-base/nodes/Phantombuster/GenericFunctions.ts index 1c15025abe..3be209ea32 100644 --- a/packages/nodes-base/nodes/Phantombuster/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Phantombuster/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function phantombusterApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise { // tslint:disable-line:no-any @@ -33,24 +33,16 @@ export async function phantombusterApiRequest(this: IExecuteFunctions | IExecute //@ts-ignore return await this.helpers.request.call(this, options); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - const message = error.response.body.error; - // Try to return the error prettier - throw new Error( - `Phantombuster error response [${error.statusCode}]: ${message}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } -export function validateJSON(json: string | undefined, name: string): any { // tslint:disable-line:no-any +export function validateJSON(self: IExecuteFunctions, json: string | undefined, name: string) { let result; try { result = JSON.parse(json!); } catch (exception) { - throw new Error(`${name} must provide a valid JSON`); + throw new NodeOperationError(self.getNode(), `${name} must provide a valid JSON`); } return result; } diff --git a/packages/nodes-base/nodes/Phantombuster/Phantombuster.node.ts b/packages/nodes-base/nodes/Phantombuster/Phantombuster.node.ts index c723066d86..8c1616b4fe 100644 --- a/packages/nodes-base/nodes/Phantombuster/Phantombuster.node.ts +++ b/packages/nodes-base/nodes/Phantombuster/Phantombuster.node.ts @@ -216,11 +216,12 @@ export class Phantombuster implements INodeType { if (jsonParameters) { if (additionalFields.argumentsJson) { - body.arguments = validateJSON(additionalFields.argumentsJson as string, 'Arguments'); + body.arguments = validateJSON(this, additionalFields.argumentsJson as string, 'Arguments'); + delete additionalFields.argumentsJson; } if (additionalFields.bonusArgumentJson) { - body.bonusArgument = validateJSON(additionalFields.bonusArgumentJson as string, 'Bonus Argument'); + body.bonusArgument = validateJSON(this, additionalFields.bonusArgumentJson as string, 'Bonus Argument'); delete additionalFields.bonusArgumentJson; } } else { diff --git a/packages/nodes-base/nodes/PhilipsHue/GenericFunctions.ts b/packages/nodes-base/nodes/PhilipsHue/GenericFunctions.ts index 352777632d..b7bd4fd1ab 100644 --- a/packages/nodes-base/nodes/PhilipsHue/GenericFunctions.ts +++ b/packages/nodes-base/nodes/PhilipsHue/GenericFunctions.ts @@ -8,7 +8,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function philipsHueApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -38,16 +38,7 @@ export async function philipsHueApiRequest(this: IExecuteFunctions | ILoadOption //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'philipsHueOAuth2Api', options, { tokenType: 'Bearer' }); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - const errorMessage = error.response.body.error.description; - - // Try to return the error prettier - throw new Error( - `Philip Hue error response [${error.statusCode}]: ${errorMessage}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts b/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts index 408a32b8fe..2352a7689e 100644 --- a/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts @@ -6,6 +6,8 @@ import { import { IDataObject, ILoadOptionsFunctions, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -71,7 +73,7 @@ export async function pipedriveApiRequest(this: IHookFunctions | IExecuteFunctio const credentials = this.getCredentials('pipedriveApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } query.api_token = credentials.apiToken; @@ -89,7 +91,7 @@ export async function pipedriveApiRequest(this: IHookFunctions | IExecuteFunctio } if (responseData.success === false) { - throw new Error(`Pipedrive error response: ${responseData.error} (${responseData.error_info})`); + throw new NodeApiError(this.getNode(), responseData); } return { @@ -97,22 +99,7 @@ export async function pipedriveApiRequest(this: IHookFunctions | IExecuteFunctio data: responseData.data, }; } catch(error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The Pipedrive credentials are not valid!'); - } - - if (error.response && error.response.body && error.response.body.error) { - // Try to return the error prettier - let errorMessage = `Pipedrive error response [${error.statusCode}]: ${error.response.body.error.message}`; - if (error.response.body.error_info) { - errorMessage += ` - ${error.response.body.error_info}`; - } - throw new Error(errorMessage); - } - - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } @@ -182,7 +169,7 @@ export async function pipedriveGetCustomProperties(this: IHookFunctions | IExecu }; if (endpoints[resource] === undefined) { - throw new Error(`The resource "${resource}" is not supported for resolving custom values!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not supported for resolving custom values!`); } const requestMethod = 'GET'; diff --git a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts index c9aeda94ce..cc95485ab7 100644 --- a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts +++ b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -2707,13 +2708,13 @@ export class Pipedrive implements INodeType { const item = items[i]; 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; 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); @@ -3014,7 +3015,7 @@ export class Pipedrive implements INodeType { } } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } let responseData; diff --git a/packages/nodes-base/nodes/Pipedrive/PipedriveTrigger.node.ts b/packages/nodes-base/nodes/Pipedrive/PipedriveTrigger.node.ts index eb1b10f747..d9cdc3e542 100644 --- a/packages/nodes-base/nodes/Pipedrive/PipedriveTrigger.node.ts +++ b/packages/nodes-base/nodes/Pipedrive/PipedriveTrigger.node.ts @@ -300,7 +300,7 @@ export class PipedriveTrigger implements INodeType { try { await pipedriveApiRequest.call(this, 'DELETE', endpoint, body); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/Plivo/GenericFunctions.ts b/packages/nodes-base/nodes/Plivo/GenericFunctions.ts index 6af3a48acd..475b899c17 100644 --- a/packages/nodes-base/nodes/Plivo/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Plivo/GenericFunctions.ts @@ -5,6 +5,8 @@ import { import { IDataObject, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; /** @@ -27,7 +29,7 @@ export async function plivoApiRequest( const credentials = this.getCredentials('plivoApi') as { authId: string, authToken: string }; if (!credentials) { - throw new Error('No credentials returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials returned!'); } const options = { @@ -45,18 +47,6 @@ export async function plivoApiRequest( try { return await this.helpers.request(options); } catch (error) { - if (error.statusCode === 401) { - throw new Error('Invalid Plivo credentials'); - } - if (error?.response?.body?.error) { - let errorMessage = `Plivo error response [${error.statusCode}]: ${error.response.body.error}`; - if (error.response.body.more_info) { - errorMessage = `errorMessage (${error.response.body.more_info})`; - } - - throw new Error(errorMessage); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/PostHog/GenericFunctions.ts b/packages/nodes-base/nodes/PostHog/GenericFunctions.ts index ad64cc006d..0b4cec52cc 100644 --- a/packages/nodes-base/nodes/PostHog/GenericFunctions.ts +++ b/packages/nodes-base/nodes/PostHog/GenericFunctions.ts @@ -8,7 +8,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function posthogApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise { // tslint:disable-line:no-any @@ -36,15 +36,7 @@ export async function posthogApiRequest(this: IExecuteFunctions | ILoadOptionsFu } return await this.helpers.request!(options); } catch (error) { - if (error.response && error.response.body && error.response.body.message) { - - const message = error.response.body.message; - // Try to return the error prettier - throw new Error( - `PosHog error response [${error.statusCode}]: ${message}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.ts index 1250236d78..9b62d57db5 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.ts @@ -3,7 +3,8 @@ import { IDataObject, INodeExecutionData, INodeType, - INodeTypeDescription + INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import * as pgPromise from 'pg-promise'; @@ -197,7 +198,7 @@ export class Postgres implements INodeType { const credentials = this.getCredentials('postgres'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const pgp = pgPromise(); @@ -260,7 +261,7 @@ export class Postgres implements INodeType { returnItems = this.helpers.returnJsonArray(updateItems); } else { 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 diff --git a/packages/nodes-base/nodes/Postmark/GenericFunctions.ts b/packages/nodes-base/nodes/Postmark/GenericFunctions.ts index 5b03ebf269..6caf415079 100644 --- a/packages/nodes-base/nodes/Postmark/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Postmark/GenericFunctions.ts @@ -10,7 +10,9 @@ import { import { IDataObject, IHookFunctions, - IWebhookFunctions + IWebhookFunctions, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; @@ -18,7 +20,7 @@ export async function postmarkApiRequest(this: IExecuteFunctions | IWebhookFunct const credentials = this.getCredentials('postmarkApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { @@ -40,7 +42,7 @@ export async function postmarkApiRequest(this: IExecuteFunctions | IWebhookFunct try { return await this.helpers.request!(options); } catch (error) { - throw new Error(`Postmark: ${error.statusCode} Message: ${error.message}`); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Postmark/PostmarkTrigger.node.ts b/packages/nodes-base/nodes/Postmark/PostmarkTrigger.node.ts index e6d86b5159..a66f099399 100644 --- a/packages/nodes-base/nodes/Postmark/PostmarkTrigger.node.ts +++ b/packages/nodes-base/nodes/Postmark/PostmarkTrigger.node.ts @@ -236,7 +236,7 @@ export class PostmarkTrigger implements INodeType { try { await postmarkApiRequest.call(this, 'DELETE', endpoint, body); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/ProfitWell/GenericFunctions.ts b/packages/nodes-base/nodes/ProfitWell/GenericFunctions.ts index 4561bbef59..5ad323fbc4 100644 --- a/packages/nodes-base/nodes/ProfitWell/GenericFunctions.ts +++ b/packages/nodes-base/nodes/ProfitWell/GenericFunctions.ts @@ -10,14 +10,14 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function profitWellApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any try { const credentials = this.getCredentials('profitWellApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { headers: { @@ -34,15 +34,7 @@ export async function profitWellApiRequest(this: IHookFunctions | IExecuteFuncti return await this.helpers.request!(options); } catch (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(`ProfitWell error response [${error.statusCode}]: ${errorBody.message}`); - } - - // Expected error data did not get returned so throw the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Pushbullet/GenericFunctions.ts b/packages/nodes-base/nodes/Pushbullet/GenericFunctions.ts index bf412da333..6b152f5d34 100644 --- a/packages/nodes-base/nodes/Pushbullet/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Pushbullet/GenericFunctions.ts @@ -8,7 +8,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function pushbulletApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, uri?: string | undefined, option = {}): Promise { // tslint:disable-line:no-any @@ -30,16 +30,7 @@ export async function pushbulletApiRequest(this: IExecuteFunctions | ILoadOption //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'pushbulletOAuth2Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - const message = error.response.body.error.message; - - // Try to return the error prettier - throw new Error( - `Pushbullet error response [${error.statusCode}]: ${message}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Pushbullet/Pushbullet.node.ts b/packages/nodes-base/nodes/Pushbullet/Pushbullet.node.ts index 283715839b..8e38f2c45d 100644 --- a/packages/nodes-base/nodes/Pushbullet/Pushbullet.node.ts +++ b/packages/nodes-base/nodes/Pushbullet/Pushbullet.node.ts @@ -11,6 +11,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -486,11 +487,11 @@ export class Pushbullet implements INodeType { const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; 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 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]; diff --git a/packages/nodes-base/nodes/Pushcut/GenericFunctions.ts b/packages/nodes-base/nodes/Pushcut/GenericFunctions.ts index 269df83578..3804132044 100644 --- a/packages/nodes-base/nodes/Pushcut/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Pushcut/GenericFunctions.ts @@ -10,6 +10,7 @@ import { import { IDataObject, IHookFunctions, + NodeApiError, } from 'n8n-workflow'; export async function pushcutApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, uri?: string | undefined, option = {}): Promise { // tslint:disable-line:no-any @@ -36,15 +37,6 @@ export async function pushcutApiRequest(this: IExecuteFunctions | ILoadOptionsFu //@ts-ignore return await this.helpers.request.call(this, options); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - const message = error.response.body.error; - - // Try to return the error prettier - throw new Error( - `Pushcut error response [${error.statusCode}]: ${message}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Pushcut/PushcutTrigger.node.ts b/packages/nodes-base/nodes/Pushcut/PushcutTrigger.node.ts index 024ca035d9..3e7e4df6c4 100644 --- a/packages/nodes-base/nodes/Pushcut/PushcutTrigger.node.ts +++ b/packages/nodes-base/nodes/Pushcut/PushcutTrigger.node.ts @@ -105,7 +105,7 @@ export class PushcutTrigger implements INodeType { try { await pushcutApiRequest.call(this, 'DELETE', endpoint); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/Pushover/GenericFunctions.ts b/packages/nodes-base/nodes/Pushover/GenericFunctions.ts index 1708288a36..4fe876538c 100644 --- a/packages/nodes-base/nodes/Pushover/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Pushover/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function pushoverApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise { // tslint:disable-line:no-any @@ -36,16 +36,6 @@ export async function pushoverApiRequest(this: IExecuteFunctions | IExecuteSingl //@ts-ignore return await this.helpers.request.call(this, options); } catch (error) { - if (error.response && error.response.body && error.response.body.errors) { - - let errors = error.response.body.errors; - - errors = errors.map((e: IDataObject) => e); - // Try to return the error prettier - throw new Error( - `PushOver error response [${error.statusCode}]: ${errors.join('|')}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Pushover/Pushover.node.ts b/packages/nodes-base/nodes/Pushover/Pushover.node.ts index 3d290ecf00..10115f9bcb 100644 --- a/packages/nodes-base/nodes/Pushover/Pushover.node.ts +++ b/packages/nodes-base/nodes/Pushover/Pushover.node.ts @@ -12,6 +12,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -354,7 +355,7 @@ export class Pushover implements INodeType { const binaryPropertyName = attachment.binaryPropertyName as string; 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!'); } const item = items[i].binary as IBinaryKeyData; @@ -362,7 +363,7 @@ export class Pushover implements INodeType { const binaryData = item[binaryPropertyName] as IBinaryData; if (binaryData === 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!`); } body.attachment = { diff --git a/packages/nodes-base/nodes/QuestDb/QuestDb.node.ts b/packages/nodes-base/nodes/QuestDb/QuestDb.node.ts index c4b9036f0e..e3f8b124bc 100644 --- a/packages/nodes-base/nodes/QuestDb/QuestDb.node.ts +++ b/packages/nodes-base/nodes/QuestDb/QuestDb.node.ts @@ -4,6 +4,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import * as pgPromise from 'pg-promise'; @@ -128,7 +129,7 @@ export class QuestDb implements INodeType { const credentials = this.getCredentials('questDb'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const pgp = pgPromise(); @@ -188,7 +189,7 @@ export class QuestDb implements INodeType { returnItems = this.helpers.returnJsonArray(returnedItems as IDataObject[]); } else { 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 diff --git a/packages/nodes-base/nodes/QuickBase/GenericFunctions.ts b/packages/nodes-base/nodes/QuickBase/GenericFunctions.ts index 365ffb87d9..255d3d6d85 100644 --- a/packages/nodes-base/nodes/QuickBase/GenericFunctions.ts +++ b/packages/nodes-base/nodes/QuickBase/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function quickbaseApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -18,15 +18,15 @@ export async function quickbaseApiRequest(this: IExecuteFunctions | ILoadOptions const credentials = this.getCredentials('quickbaseApi') as IDataObject; if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } if (!credentials.hostname) { - throw new Error('Hostname must be defined'); + throw new NodeOperationError(this.getNode(), 'Hostname must be defined'); } if (!credentials.userToken) { - throw new Error('User Token must be defined'); + throw new NodeOperationError(this.getNode(), 'User Token must be defined'); } try { @@ -57,17 +57,7 @@ export async function quickbaseApiRequest(this: IExecuteFunctions | ILoadOptions //@ts-ignore return await this.helpers?.request(options); } catch (error) { - - if (error.response && error.response.body && error.response.body.description) { - - const message = error.response.body.description; - - // Try to return the error prettier - throw new Error( - `Quickbase error response [${error.statusCode}]: ${message} (qb-api-ray=${error.response.headers['qb-api-ray']})`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/QuickBase/QuickBase.node.ts b/packages/nodes-base/nodes/QuickBase/QuickBase.node.ts index c47cc05b2c..a3de7a4683 100644 --- a/packages/nodes-base/nodes/QuickBase/QuickBase.node.ts +++ b/packages/nodes-base/nodes/QuickBase/QuickBase.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -386,7 +387,7 @@ export class QuickBase implements INodeType { } if (items[i].json[updateKey] === undefined) { - throw new Error(`The update key ${updateKey} could not be found in the input`); + throw new NodeOperationError(this.getNode(), `The update key ${updateKey} could not be found in the input`); } record[fieldsLabelKey['Record ID#']] = { value: items[i].json[updateKey] }; @@ -457,7 +458,7 @@ export class QuickBase implements INodeType { } if (items[i].json[updateKey] === undefined) { - throw new Error(`The update key ${updateKey} could not be found in the input`); + throw new NodeOperationError(this.getNode(), `The update key ${updateKey} could not be found in the input`); } record[mergeFieldId] = { value: items[i].json[updateKey] }; diff --git a/packages/nodes-base/nodes/QuickBooks/GenericFunctions.ts b/packages/nodes-base/nodes/QuickBooks/GenericFunctions.ts index 9218f82bad..3d9953a216 100644 --- a/packages/nodes-base/nodes/QuickBooks/GenericFunctions.ts +++ b/packages/nodes-base/nodes/QuickBooks/GenericFunctions.ts @@ -8,6 +8,7 @@ import { ILoadOptionsFunctions, INodeExecutionData, INodePropertyOptions, + NodeApiError, } from 'n8n-workflow'; import { @@ -95,18 +96,7 @@ export async function quickBooksApiRequest( try { return await this.helpers.requestOAuth2!.call(this, 'quickBooksOAuth2Api', options); } catch (error) { - - const errors = error.error.Fault.Error; - - if (errors && Array.isArray(errors)) { - const errorMessage = errors.map( - (e) => `QuickBooks error response [${e.code}]: ${e.Message} - Detail: ${e.Detail}`, - ).join('|'); - - throw new Error(errorMessage); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/QuickBooks/QuickBooks.node.ts b/packages/nodes-base/nodes/QuickBooks/QuickBooks.node.ts index 4fe8f983df..98645002ee 100644 --- a/packages/nodes-base/nodes/QuickBooks/QuickBooks.node.ts +++ b/packages/nodes-base/nodes/QuickBooks/QuickBooks.node.ts @@ -8,6 +8,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -182,18 +183,18 @@ export class QuickBooks implements INodeType { const lines = this.getNodeParameter('Line', i) as IDataObject[]; if (!lines.length) { - throw new Error(`Please enter at least one line for the ${resource}.`); + throw new NodeOperationError(this.getNode(), `Please enter at least one line for the ${resource}.`); } if (lines.some(line => line.DetailType === undefined || line.Amount === undefined || line.Description === undefined)) { - throw new Error('Please enter detail type, amount and description for every line.'); + throw new NodeOperationError(this.getNode(), 'Please enter detail type, amount and description for every line.'); } lines.forEach(line => { if (line.DetailType === 'AccountBasedExpenseLineDetail' && line.accountId === undefined) { - throw new Error('Please enter an account ID for the associated line.'); + throw new NodeOperationError(this.getNode(), 'Please enter an account ID for the associated line.'); } else if (line.DetailType === 'ItemBasedExpenseLineDetail' && line.itemId === undefined) { - throw new Error('Please enter an item ID for the associated line.'); + throw new NodeOperationError(this.getNode(), 'Please enter an item ID for the associated line.'); } }); @@ -273,7 +274,7 @@ export class QuickBooks implements INodeType { const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; 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}.`); } body = populateFields.call(this, body, updateFields, resource); @@ -345,7 +346,7 @@ export class QuickBooks implements INodeType { const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; 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}.`); } body = populateFields.call(this, body, updateFields, resource); @@ -416,7 +417,7 @@ export class QuickBooks implements INodeType { const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; 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}.`); } body = populateFields.call(this, body, updateFields, resource); @@ -444,16 +445,16 @@ export class QuickBooks implements INodeType { const lines = this.getNodeParameter('Line', i) as IDataObject[]; if (!lines.length) { - throw new Error(`Please enter at least one line for the ${resource}.`); + throw new NodeOperationError(this.getNode(), `Please enter at least one line for the ${resource}.`); } if (lines.some(line => line.DetailType === undefined || line.Amount === undefined || line.Description === undefined)) { - throw new Error('Please enter detail type, amount and description for every line.'); + throw new NodeOperationError(this.getNode(), 'Please enter detail type, amount and description for every line.'); } lines.forEach(line => { if (line.DetailType === 'SalesItemLineDetail' && line.itemId === undefined) { - throw new Error('Please enter an item ID for the associated line.'); + throw new NodeOperationError(this.getNode(), 'Please enter an item ID for the associated line.'); } }); @@ -559,7 +560,7 @@ export class QuickBooks implements INodeType { const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; 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}.`); } body = populateFields.call(this, body, updateFields, resource); @@ -587,16 +588,16 @@ export class QuickBooks implements INodeType { const lines = this.getNodeParameter('Line', i) as IDataObject[]; if (!lines.length) { - throw new Error(`Please enter at least one line for the ${resource}.`); + throw new NodeOperationError(this.getNode(), `Please enter at least one line for the ${resource}.`); } if (lines.some(line => line.DetailType === undefined || line.Amount === undefined || line.Description === undefined)) { - throw new Error('Please enter detail type, amount and description for every line.'); + throw new NodeOperationError(this.getNode(), 'Please enter detail type, amount and description for every line.'); } lines.forEach(line => { if (line.DetailType === 'SalesItemLineDetail' && line.itemId === undefined) { - throw new Error('Please enter an item ID for the associated line.'); + throw new NodeOperationError(this.getNode(), 'Please enter an item ID for the associated line.'); } }); @@ -702,7 +703,7 @@ export class QuickBooks implements INodeType { const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; 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}.`); } body = populateFields.call(this, body, updateFields, resource); @@ -874,7 +875,7 @@ export class QuickBooks implements INodeType { const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; 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}.`); } body = populateFields.call(this, body, updateFields, resource); @@ -962,7 +963,7 @@ export class QuickBooks implements INodeType { const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; 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}.`); } body = populateFields.call(this, body, updateFields, resource); diff --git a/packages/nodes-base/nodes/RabbitMQ/RabbitMQ.node.ts b/packages/nodes-base/nodes/RabbitMQ/RabbitMQ.node.ts index 3ce903d913..a4db2f278e 100644 --- a/packages/nodes-base/nodes/RabbitMQ/RabbitMQ.node.ts +++ b/packages/nodes-base/nodes/RabbitMQ/RabbitMQ.node.ts @@ -7,6 +7,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -333,7 +335,7 @@ export class RabbitMQ implements INodeType { if (response!.status !== 'fulfilled') { if (this.continueOnFail() !== true) { - throw new Error(response!.reason as string); + throw new NodeApiError(this.getNode(), response); } else { // Return the actual reason as error returnItems.push( @@ -398,7 +400,7 @@ export class RabbitMQ implements INodeType { if (response!.status !== 'fulfilled') { if (this.continueOnFail() !== true) { - throw new Error(response!.reason as string); + throw new NodeApiError(this.getNode(), response); } else { // Return the actual reason as error returnItems.push( @@ -422,7 +424,7 @@ export class RabbitMQ implements INodeType { await channel.close(); await channel.connection.close(); } else { - throw new Error(`The operation "${mode}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${mode}" is not known!`); } return this.prepareOutputData(returnItems); diff --git a/packages/nodes-base/nodes/Raindrop/GenericFunctions.ts b/packages/nodes-base/nodes/Raindrop/GenericFunctions.ts index ff50006513..9b05f10b4a 100644 --- a/packages/nodes-base/nodes/Raindrop/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Raindrop/GenericFunctions.ts @@ -6,6 +6,7 @@ import { import { IDataObject, ILoadOptionsFunctions, + NodeApiError, } from 'n8n-workflow'; import { @@ -50,14 +51,7 @@ export async function raindropApiRequest( try { return await this.helpers.requestOAuth2!.call(this, 'raindropOAuth2Api', options); - } catch (error) { - - if (error?.response?.body?.errorMessage) { - const errorMessage = error?.response?.body?.errorMessage; - throw new Error(`Raindrop error response [${error.statusCode}]: ${errorMessage}`); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Raindrop/Raindrop.node.ts b/packages/nodes-base/nodes/Raindrop/Raindrop.node.ts index 7792df40a8..084c22cf5d 100644 --- a/packages/nodes-base/nodes/Raindrop/Raindrop.node.ts +++ b/packages/nodes-base/nodes/Raindrop/Raindrop.node.ts @@ -10,6 +10,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -204,7 +205,7 @@ export class Raindrop implements INodeType { const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; 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}.`); } Object.assign(body, updateFields); @@ -314,7 +315,7 @@ export class Raindrop implements INodeType { const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; 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}.`); } if (updateFields.parentId) { @@ -333,11 +334,11 @@ export class Raindrop implements INodeType { if (updateFields.cover) { if (!items[i].binary) { - throw new Error('No binary data exists on item!'); + throw new NodeOperationError(this.getNode(), 'No binary data exists on item!'); } if (!updateFields.cover) { - throw new Error('Please enter a binary property to upload a cover image.'); + throw new NodeOperationError(this.getNode(), 'Please enter a binary property to upload a cover image.'); } const binaryPropertyName = updateFields.cover as string; diff --git a/packages/nodes-base/nodes/ReadBinaryFile.node.ts b/packages/nodes-base/nodes/ReadBinaryFile.node.ts index bfabc72ba3..6b595d3b10 100644 --- a/packages/nodes-base/nodes/ReadBinaryFile.node.ts +++ b/packages/nodes-base/nodes/ReadBinaryFile.node.ts @@ -3,6 +3,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -66,7 +67,7 @@ export class ReadBinaryFile implements INodeType { data = await fsReadFileAsync(filePath) as Buffer; } catch (error) { if (error.code === 'ENOENT') { - throw new Error(`The file "${filePath}" could not be found.`); + throw new NodeOperationError(this.getNode(), `The file "${filePath}" could not be found.`); } throw error; diff --git a/packages/nodes-base/nodes/Reddit/GenericFunctions.ts b/packages/nodes-base/nodes/Reddit/GenericFunctions.ts index 32aa3d899f..f9cb55b110 100644 --- a/packages/nodes-base/nodes/Reddit/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Reddit/GenericFunctions.ts @@ -4,7 +4,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import { @@ -42,36 +42,18 @@ export async function redditApiRequest( } if (authRequired) { - let response; - try { - response = await this.helpers.requestOAuth2.call(this, 'redditOAuth2Api', options); + return await this.helpers.requestOAuth2.call(this, 'redditOAuth2Api', options); } catch (error) { - if (error.response.body && error.response.body.message) { - const message = error.response.body.message; - throw new Error(`Reddit error response [${error.statusCode}]: ${message}`); - } + throw new NodeApiError(this.getNode(), error); } - if ((response.errors && response.errors.length !== 0) || (response.json && response.json.errors && response.json.errors.length !== 0)) { - const errors = response?.errors || response?.json?.errors; - const errorMessage = errors.map((error: []) => error.join('-')); - - throw new Error(`Reddit error response [400]: ${errorMessage.join('|')}`); - } - - return response; - } else { try { return await this.helpers.request.call(this, options); } catch (error) { - const errorMessage = error?.response?.body?.message; - if (errorMessage) { - throw new Error(`Reddit error response [${error.statusCode}]: ${errorMessage}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } } diff --git a/packages/nodes-base/nodes/Reddit/Reddit.node.ts b/packages/nodes-base/nodes/Reddit/Reddit.node.ts index 093414e021..2d5257bf33 100644 --- a/packages/nodes-base/nodes/Reddit/Reddit.node.ts +++ b/packages/nodes-base/nodes/Reddit/Reddit.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, } from 'n8n-workflow'; import { @@ -359,12 +360,12 @@ export class Reddit implements INodeType { } else if (details === 'friends') { responseData = responseData.data.children; if (!responseData.length) { - throw new Error('Reddit error response [404]: Not Found'); + throw new NodeApiError(this.getNode(), responseData); } } else if (details === 'karma') { responseData = responseData.data; if (!responseData.length) { - throw new Error('Reddit error response [404]: Not Found'); + throw new NodeApiError(this.getNode(), responseData); } } else if (details === 'trophies') { responseData = responseData.data.trophies.map((trophy: IDataObject) => trophy.data); diff --git a/packages/nodes-base/nodes/Redis/Redis.node.ts b/packages/nodes-base/nodes/Redis/Redis.node.ts index 3d1aafa962..ab01f3a0b3 100644 --- a/packages/nodes-base/nodes/Redis/Redis.node.ts +++ b/packages/nodes-base/nodes/Redis/Redis.node.ts @@ -5,6 +5,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { set } from 'lodash'; @@ -382,7 +383,7 @@ export class Redis implements INodeType { } - async function setValue(client: redis.RedisClient, keyName: string, value: string | number | object | string[] | number[], expire: boolean, ttl: number, type?: string) { + const setValue = async (client: redis.RedisClient, keyName: string, value: string | number | object | string[] | number[], expire: boolean, ttl: number, type?: string) => { if (type === undefined || type === 'automatic') { // Request the type first if (typeof value === 'string') { @@ -392,7 +393,7 @@ export class Redis implements INodeType { } else if (typeof value === 'object') { type = 'hash'; } else { - throw new Error('Could not identify the type to set. Please set it manually!'); + throw new NodeOperationError(this.getNode(), 'Could not identify the type to set. Please set it manually!'); } } @@ -417,7 +418,7 @@ export class Redis implements INodeType { await clientExpire(keyName, ttl); } return; - } + }; return new Promise((resolve, reject) => { @@ -428,7 +429,7 @@ export class Redis implements INodeType { const credentials = this.getCredentials('redis'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const redisOptions: redis.ClientOpts = { diff --git a/packages/nodes-base/nodes/Rocketchat/GenericFunctions.ts b/packages/nodes-base/nodes/Rocketchat/GenericFunctions.ts index 2078470d13..5ed00d3601 100644 --- a/packages/nodes-base/nodes/Rocketchat/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Rocketchat/GenericFunctions.ts @@ -7,12 +7,13 @@ import { IHookFunctions, ILoadOptionsFunctions, } from 'n8n-core'; +import { NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function rocketchatApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, resource: string, method: string, operation: string, body: any = {}, headers?: object): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('rocketchatApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const headerWithAuthentication = Object.assign({}, headers, @@ -35,15 +36,7 @@ export async function rocketchatApiRequest(this: IExecuteFunctions | ILoadOption try { return await this.helpers.request!(options); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - const errorMessage = error.response.body.error; - // Try to return the error prettier - throw new Error( - `Rocketchat error response [${error.statusCode}]: ${errorMessage}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/RssFeedRead.node.ts b/packages/nodes-base/nodes/RssFeedRead.node.ts index 503a2f0cf7..03e13dc822 100644 --- a/packages/nodes-base/nodes/RssFeedRead.node.ts +++ b/packages/nodes-base/nodes/RssFeedRead.node.ts @@ -4,6 +4,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import * as Parser from 'rss-parser'; @@ -40,7 +41,7 @@ export class RssFeedRead implements INodeType { const url = this.getNodeParameter('url', 0) as string; if (!url) { - throw new Error('The parameter "URL" has to be set!'); + throw new NodeOperationError(this.getNode(), 'The parameter "URL" has to be set!'); } // TODO: Later add also check if the url has a valid format @@ -49,12 +50,12 @@ export class RssFeedRead implements INodeType { let feed: Parser.Output; try { feed = await parser.parseURL(url); - } catch (e) { - if (e.code === 'ECONNREFUSED') { - throw new Error(`It was not possible to connect to the URL. Please make sure the URL "${url}" it is valid!`); + } catch (error) { + if (error.code === 'ECONNREFUSED') { + throw new NodeOperationError(this.getNode(), `It was not possible to connect to the URL. Please make sure the URL "${url}" it is valid!`); } - throw e; + throw new NodeOperationError(this.getNode(), error); } diff --git a/packages/nodes-base/nodes/Rundeck/Rundeck.node.ts b/packages/nodes-base/nodes/Rundeck/Rundeck.node.ts index 342c3c4bc6..15a75c3c12 100644 --- a/packages/nodes-base/nodes/Rundeck/Rundeck.node.ts +++ b/packages/nodes-base/nodes/Rundeck/Rundeck.node.ts @@ -4,6 +4,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { RundeckApi } from './RundeckApi'; @@ -185,10 +186,10 @@ export class Rundeck implements INodeType { returnData.push(response); } else { - throw new Error(`The operation "${operation}" is not supported!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not supported!`); } } else { - throw new Error(`The resource "${resource}" is not supported!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not supported!`); } } diff --git a/packages/nodes-base/nodes/Rundeck/RundeckApi.ts b/packages/nodes-base/nodes/Rundeck/RundeckApi.ts index 80b3a91fea..890d595ccd 100644 --- a/packages/nodes-base/nodes/Rundeck/RundeckApi.ts +++ b/packages/nodes-base/nodes/Rundeck/RundeckApi.ts @@ -1,6 +1,6 @@ import { OptionsWithUri } from 'request'; import { IExecuteFunctions } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, NodeApiError, NodeOperationError } from 'n8n-workflow'; export interface RundeckCredentials { url: string; @@ -13,15 +13,15 @@ export class RundeckApi { constructor(executeFunctions: IExecuteFunctions) { - const credentials = executeFunctions.getCredentials('rundeckApi'); + this.executeFunctions = executeFunctions; + if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.executeFunctions.getNode(), 'No credentials got returned!'); } this.credentials = credentials as unknown as RundeckCredentials; - this.executeFunctions = executeFunctions; } @@ -43,12 +43,7 @@ export class RundeckApi { try { return await this.executeFunctions.helpers.request!(options); } catch (error) { - let errorMessage = error.message; - if (error.response && error.response.body && error.response.body.message) { - errorMessage = error.response.body.message.replace('\n', ''); - } - - throw Error(`Rundeck Error [${error.statusCode}]: ${errorMessage}`); + throw new NodeApiError(this.executeFunctions.getNode(), error); } } diff --git a/packages/nodes-base/nodes/S3/GenericFunctions.ts b/packages/nodes-base/nodes/S3/GenericFunctions.ts index 111da29b85..1e53f85073 100644 --- a/packages/nodes-base/nodes/S3/GenericFunctions.ts +++ b/packages/nodes-base/nodes/S3/GenericFunctions.ts @@ -22,7 +22,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import { URL } from 'url'; @@ -34,11 +34,11 @@ export async function s3ApiRequest(this: IHookFunctions | IExecuteFunctions | IL credentials = this.getCredentials('s3'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } if (!(credentials.endpoint as string).startsWith('http')) { - throw new Error('HTTP(S) Scheme is required in endpoint definition'); + throw new NodeOperationError(this.getNode(), 'HTTP(S) Scheme is required in endpoint definition'); } const endpoint = new URL(credentials.endpoint as string); @@ -80,17 +80,7 @@ export async function s3ApiRequest(this: IHookFunctions | IExecuteFunctions | IL try { return await this.helpers.request!(options); } catch (error) { - const errorMessage = error.response?.body.message || error.response?.body.Message || error.message; - - if (error.statusCode === 403) { - if (errorMessage === 'The security token included in the request is invalid.') { - throw new Error('The S3 credentials are not valid!'); - } else if (errorMessage.startsWith('The request signature we calculated does not match the signature you provided')) { - throw new Error('The S3 credentials are not valid!'); - } - } - - throw new Error(`S3 error response [${error.statusCode}]: ${errorMessage}`); + throw new NodeApiError(this.getNode(), error); } } @@ -98,7 +88,7 @@ export async function s3ApiRequestREST(this: IHookFunctions | IExecuteFunctions const response = await s3ApiRequest.call(this, bucket, method, path, body, query, headers, options, region); try { return JSON.parse(response); - } catch (e) { + } catch (error) { return response; } } @@ -114,8 +104,8 @@ export async function s3ApiRequestSOAP(this: IHookFunctions | IExecuteFunctions resolve(data); }); }); - } catch (e) { - return e; + } catch (error) { + return error; } } diff --git a/packages/nodes-base/nodes/S3/S3.node.ts b/packages/nodes-base/nodes/S3/S3.node.ts index 7420f16f25..70c7f3dbd3 100644 --- a/packages/nodes-base/nodes/S3/S3.node.ts +++ b/packages/nodes-base/nodes/S3/S3.node.ts @@ -23,6 +23,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -119,7 +121,7 @@ export class S3 implements INodeType { try { credentials = this.getCredentials('s3'); } catch (error) { - throw new Error(error); + throw new NodeApiError(this.getNode(), error); } const name = this.getNodeParameter('name', i) as string; @@ -430,7 +432,7 @@ export class S3 implements INodeType { const fileName = fileKey.split('/')[fileKey.split('/').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 s3ApiRequestSOAP.call(this, bucketName, 'GET', '', '', { location: '' }); @@ -597,11 +599,11 @@ export class S3 implements INodeType { const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string; 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) { - 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]; diff --git a/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts b/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts index 037b94afc1..54be4408c8 100644 --- a/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts @@ -11,6 +11,7 @@ import { import { IDataObject, INodePropertyOptions, + NodeApiError, } from 'n8n-workflow'; import * as moment from 'moment-timezone'; @@ -41,11 +42,7 @@ export async function salesforceApiRequest(this: IExecuteFunctions | IExecuteSin return await this.helpers.requestOAuth2.call(this, credentialsType, options); } } catch (error) { - if (error.response && error.response.body && error.response.body[0] && error.response.body[0].message) { - // Try to return the error prettier - throw new Error(`Salesforce error response [${error.statusCode}]: ${error.response.body[0].message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Salesforce/Salesforce.node.ts b/packages/nodes-base/nodes/Salesforce/Salesforce.node.ts index 031280d0b3..9cbe6ee2c8 100644 --- a/packages/nodes-base/nodes/Salesforce/Salesforce.node.ts +++ b/packages/nodes-base/nodes/Salesforce/Salesforce.node.ts @@ -9,6 +9,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -950,7 +952,7 @@ export class Salesforce implements INodeType { const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; const body: ILead = {}; if (!Object.keys(updateFields).length) { - throw new Error('You must add at least one update field'); + throw new NodeOperationError(this.getNode(), 'You must add at least one update field'); } if (updateFields.lastname !== undefined) { body.LastName = updateFields.lastname as string; @@ -1053,8 +1055,8 @@ export class Salesforce implements INodeType { qs.q = getQuery(options, 'Lead', returnAll, limit) as string; responseData = await salesforceApiRequestAllItems.call(this, 'records', 'GET', '/query', {}, qs); } - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.salesforce.com/docs/api-explorer/sobject/Lead/delete-lead-id @@ -1062,8 +1064,8 @@ export class Salesforce implements INodeType { const leadId = this.getNodeParameter('leadId', i) as string; try { responseData = await salesforceApiRequest.call(this, 'DELETE', `/sobjects/lead/${leadId}`); - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.salesforce.com/docs/api-explorer/sobject/Lead/get-lead @@ -1220,7 +1222,7 @@ export class Salesforce implements INodeType { const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; const body: IContact = {}; if (!Object.keys(updateFields).length) { - throw new Error('You must add at least one update field'); + throw new NodeOperationError(this.getNode(), 'You must add at least one update field'); } if (updateFields.fax !== undefined) { body.Fax = updateFields.fax as string; @@ -1341,8 +1343,8 @@ export class Salesforce implements INodeType { qs.q = getQuery(options, 'Contact', returnAll, limit) as string; responseData = await salesforceApiRequestAllItems.call(this, 'records', 'GET', '/query', {}, qs); } - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.salesforce.com/docs/api-explorer/sobject/Contact/delete-contact-id @@ -1350,8 +1352,8 @@ export class Salesforce implements INodeType { const contactId = this.getNodeParameter('contactId', i) as string; try { responseData = await salesforceApiRequest.call(this, 'DELETE', `/sobjects/contact/${contactId}`); - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.salesforce.com/docs/api-explorer/sobject/Contact/get-contact @@ -1443,8 +1445,8 @@ export class Salesforce implements INodeType { qs.q = getQuery(options, customObject, returnAll, limit) as string; responseData = await salesforceApiRequestAllItems.call(this, 'records', 'GET', '/query', {}, qs); } - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } if (operation === 'delete') { @@ -1452,8 +1454,8 @@ export class Salesforce implements INodeType { const recordId = this.getNodeParameter('recordId', i) as string; try { responseData = await salesforceApiRequest.call(this, 'DELETE', `/sobjects/${customObject}/${recordId}`); - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } } @@ -1589,8 +1591,8 @@ export class Salesforce implements INodeType { qs.q = getQuery(options, 'Opportunity', returnAll, limit) as string; responseData = await salesforceApiRequestAllItems.call(this, 'records', 'GET', '/query', {}, qs); } - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.salesforce.com/docs/api-explorer/sobject/Opportunity/delete-opportunity-id @@ -1598,8 +1600,8 @@ export class Salesforce implements INodeType { const opportunityId = this.getNodeParameter('opportunityId', i) as string; try { responseData = await salesforceApiRequest.call(this, 'DELETE', `/sobjects/opportunity/${opportunityId}`); - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.salesforce.com/docs/api-explorer/sobject/Opportunity/get-opportunity @@ -1827,8 +1829,8 @@ export class Salesforce implements INodeType { qs.q = getQuery(options, 'Account', returnAll, limit) as string; responseData = await salesforceApiRequestAllItems.call(this, 'records', 'GET', '/query', {}, qs); } - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.salesforce.com/docs/api-explorer/sobject/Account/delete-account-id @@ -1836,8 +1838,8 @@ export class Salesforce implements INodeType { const accountId = this.getNodeParameter('accountId', i) as string; try { responseData = await salesforceApiRequest.call(this, 'DELETE', `/sobjects/account/${accountId}`); - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.salesforce.com/docs/api-explorer/sobject/Account/get-account @@ -1987,8 +1989,8 @@ export class Salesforce implements INodeType { qs.q = getQuery(options, 'Case', returnAll, limit) as string; responseData = await salesforceApiRequestAllItems.call(this, 'records', 'GET', '/query', {}, qs); } - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.salesforce.com/docs/api-explorer/sobject/Case/delete-case-id @@ -1996,8 +1998,8 @@ export class Salesforce implements INodeType { const caseId = this.getNodeParameter('caseId', i) as string; try { responseData = await salesforceApiRequest.call(this, 'DELETE', `/sobjects/case/${caseId}`); - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.salesforce.com/docs/api-explorer/sobject/Case/get-case @@ -2214,8 +2216,8 @@ export class Salesforce implements INodeType { qs.q = getQuery(options, 'Task', returnAll, limit) as string; responseData = await salesforceApiRequestAllItems.call(this, 'records', 'GET', '/query', {}, qs); } - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.salesforce.com/docs/api-explorer/sobject/Task/delete-task-id @@ -2223,8 +2225,8 @@ export class Salesforce implements INodeType { const taskId = this.getNodeParameter('taskId', i) as string; try { responseData = await salesforceApiRequest.call(this, 'DELETE', `/sobjects/task/${taskId}`); - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.salesforce.com/docs/api-explorer/sobject/Task/get-task @@ -2247,7 +2249,7 @@ export class Salesforce implements INodeType { body.Body = items[i].binary![binaryPropertyName].data; body.ContentType = items[i].binary![binaryPropertyName].mimeType; } else { - throw new Error(`The property ${binaryPropertyName} does not exist`); + throw new NodeOperationError(this.getNode(), `The property ${binaryPropertyName} does not exist`); } if (additionalFields.description !== undefined) { body.Description = additionalFields.description as string; @@ -2271,7 +2273,7 @@ export class Salesforce implements INodeType { body.Body = items[i].binary![binaryPropertyName].data; body.ContentType = items[i].binary![binaryPropertyName].mimeType; } else { - throw new Error(`The property ${binaryPropertyName} does not exist`); + throw new NodeOperationError(this.getNode(), `The property ${binaryPropertyName} does not exist`); } } if (updateFields.name !== undefined) { @@ -2306,8 +2308,8 @@ export class Salesforce implements INodeType { qs.q = getQuery(options, 'Attachment', returnAll, limit) as string; responseData = await salesforceApiRequestAllItems.call(this, 'records', 'GET', '/query', {}, qs); } - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.salesforce.com/docs/api-explorer/sobject/Attachment/delete-attachment-id @@ -2315,8 +2317,8 @@ export class Salesforce implements INodeType { const attachmentId = this.getNodeParameter('attachmentId', i) as string; try { responseData = await salesforceApiRequest.call(this, 'DELETE', `/sobjects/attachment/${attachmentId}`); - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developer.salesforce.com/docs/api-explorer/sobject/Attachment/get-attachment-id @@ -2343,8 +2345,8 @@ export class Salesforce implements INodeType { qs.q = getQuery(options, 'User', returnAll, limit) as string; responseData = await salesforceApiRequestAllItems.call(this, 'records', 'GET', '/query', {}, qs); } - } catch (err) { - throw new Error(`Salesforce Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } } diff --git a/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts b/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts index a56cce6ac8..f0a7694a85 100644 --- a/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts @@ -6,12 +6,12 @@ import { ILoadOptionsFunctions, IWebhookFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function salesmateApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('salesmateApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const options: OptionsWithUri = { @@ -32,7 +32,7 @@ export async function salesmateApiRequest(this: IHookFunctions | IExecuteFunctio try { return await this.helpers.request!(options); } catch (error) { - throw new Error('Salesmate Error: ' + error); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts b/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts index 634fe47519..735d89291e 100644 --- a/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts +++ b/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts @@ -8,6 +8,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { salesmateApiRequest, @@ -313,7 +314,7 @@ export class Salesmate implements INodeType { } if (options.fields) { if ((options.fields as string).trim() === '') { - throw new Error('You have to add at least one field'); + throw new NodeOperationError(this.getNode(), 'You have to add at least one field'); } body.fields = (options.fields as string).split(',') as string[]; } else { @@ -478,7 +479,7 @@ export class Salesmate implements INodeType { } if (options.fields) { if ((options.fields as string).trim() === '') { - throw new Error('You have to add at least one field'); + throw new NodeOperationError(this.getNode(), 'You have to add at least one field'); } body.fields = (options.fields as string).split(',') as string[]; } else { @@ -670,7 +671,7 @@ export class Salesmate implements INodeType { } if (options.fields !== undefined) { if ((options.fields as string).trim() === '') { - throw new Error('You have to add at least one field'); + throw new NodeOperationError(this.getNode(), 'You have to add at least one field'); } body.fields = (options.fields as string).split(',') as string[]; } else { diff --git a/packages/nodes-base/nodes/SecurityScorecard/GenericFunctions.ts b/packages/nodes-base/nodes/SecurityScorecard/GenericFunctions.ts index d8d5c0fa7c..c2255961fd 100644 --- a/packages/nodes-base/nodes/SecurityScorecard/GenericFunctions.ts +++ b/packages/nodes-base/nodes/SecurityScorecard/GenericFunctions.ts @@ -9,14 +9,14 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function scorecardApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('securityScorecardApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const headerWithAuthentication = { Authorization: `Token ${credentials.apiKey}` }; @@ -44,10 +44,7 @@ export async function scorecardApiRequest(this: IHookFunctions | IExecuteFunctio try { return await this.helpers.request!(options); } catch (error) { - if (error.error) { - const errorMessage = `SecurityScorecard error response [${error.statusCode}]: ${error.error.error ? error.error.error.message : error.error}`; - throw new Error(errorMessage); - } else throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Segment/GenericFunctions.ts b/packages/nodes-base/nodes/Segment/GenericFunctions.ts index d80bf5cf53..bb539d7966 100644 --- a/packages/nodes-base/nodes/Segment/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Segment/GenericFunctions.ts @@ -6,12 +6,12 @@ import { ILoadOptionsFunctions, IWebhookFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function segmentApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('segmentApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const base64Key = Buffer.from(`${credentials.writekey}:`).toString('base64'); const options: OptionsWithUri = { @@ -31,6 +31,6 @@ export async function segmentApiRequest(this: IHookFunctions | IExecuteFunctions try { return await this.helpers.request!(options); } catch (error) { - throw new Error('Segment Error: ' + error); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/SendGrid/GenericFunctions.ts b/packages/nodes-base/nodes/SendGrid/GenericFunctions.ts index 0e4c81713e..fe44dbf90f 100644 --- a/packages/nodes-base/nodes/SendGrid/GenericFunctions.ts +++ b/packages/nodes-base/nodes/SendGrid/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function sendGridApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, qs: IDataObject = {}, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -41,17 +41,7 @@ export async function sendGridApiRequest(this: IHookFunctions | IExecuteFunction //@ts-ignore return await this.helpers.request!(options); } catch (error) { - if (error.response && error.response.body && error.response.body.errors) { - - let errors = error.response.body.errors; - - errors = errors.map((e: IDataObject) => e.message); - // Try to return the error prettier - throw new Error( - `SendGrid error response [${error.statusCode}]: ${errors.join('|')}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/SendGrid/SendGrid.node.ts b/packages/nodes-base/nodes/SendGrid/SendGrid.node.ts index 9565aa8917..ba9a6c3608 100644 --- a/packages/nodes-base/nodes/SendGrid/SendGrid.node.ts +++ b/packages/nodes-base/nodes/SendGrid/SendGrid.node.ts @@ -9,7 +9,8 @@ import { INodeExecutionData, INodePropertyOptions, INodeType, - INodeTypeDescription + INodeTypeDescription, + NodeOperationError } from 'n8n-workflow'; import { @@ -382,7 +383,7 @@ export class SendGrid implements INodeType { for (const property of binaryProperties) { if (!items[i].binary?.hasOwnProperty(property)) { - throw new Error(`The binary property ${property} does not exist`); + throw new NodeOperationError(this.getNode(), `The binary property ${property} does not exist`); } const binaryProperty = items[i].binary![property]; diff --git a/packages/nodes-base/nodes/Sendy/GenericFunctions.ts b/packages/nodes-base/nodes/Sendy/GenericFunctions.ts index 890c07cf31..4df80113e3 100644 --- a/packages/nodes-base/nodes/Sendy/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Sendy/GenericFunctions.ts @@ -8,7 +8,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function sendyApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise { // tslint:disable-line:no-any @@ -33,16 +33,6 @@ export async function sendyApiRequest(this: IExecuteFunctions | ILoadOptionsFunc //@ts-ignore return await this.helpers.request.call(this, options); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - let errors = error.response.body.error.errors; - - errors = errors.map((e: IDataObject) => e.message); - // Try to return the error prettier - throw new Error( - `Sendy error response [${error.statusCode}]: ${errors.join('|')}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Sendy/Sendy.node.ts b/packages/nodes-base/nodes/Sendy/Sendy.node.ts index ada1dbac26..7a010b8165 100644 --- a/packages/nodes-base/nodes/Sendy/Sendy.node.ts +++ b/packages/nodes-base/nodes/Sendy/Sendy.node.ts @@ -7,6 +7,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -159,7 +161,7 @@ export class Sendy implements INodeType { if (success.includes(responseData)) { responseData = { message: responseData }; } else { - throw new Error(`Sendy error response [${400}]: ${responseData}`); + throw new NodeApiError(this.getNode(), responseData, { httpCode: '400' }); } } } @@ -190,7 +192,7 @@ export class Sendy implements INodeType { if (responseData === '1') { responseData = { success: true }; } else { - throw new Error(`Sendy error response [${400}]: ${responseData}`); + throw new NodeOperationError(this.getNode(), `Sendy error response [${400}]: ${responseData}`); } } @@ -220,7 +222,7 @@ export class Sendy implements INodeType { if (!errors.includes(responseData)) { responseData = { count: responseData }; } else { - throw new Error(`Sendy error response [${400}]: ${responseData}`); + throw new NodeOperationError(this.getNode(), `Sendy error response [${400}]: ${responseData}`); } } @@ -245,7 +247,7 @@ export class Sendy implements INodeType { if (responseData === '1') { responseData = { success: true }; } else { - throw new Error(`Sendy error response [${400}]: ${responseData}`); + throw new NodeOperationError(this.getNode(), `Sendy error response [${400}]: ${responseData}`); } } @@ -270,7 +272,7 @@ export class Sendy implements INodeType { if (responseData === '1') { responseData = { success: true }; } else { - throw new Error(`Sendy error response [${400}]: ${responseData}`); + throw new NodeOperationError(this.getNode(), `Sendy error response [${400}]: ${responseData}`); } } @@ -304,7 +306,7 @@ export class Sendy implements INodeType { if (status.includes(responseData)) { responseData = { status: responseData }; } else { - throw new Error(`Sendy error response [${400}]: ${responseData}`); + throw new NodeOperationError(this.getNode(), `Sendy error response [${400}]: ${responseData}`); } } } diff --git a/packages/nodes-base/nodes/SentryIo/GenericFunctions.ts b/packages/nodes-base/nodes/SentryIo/GenericFunctions.ts index 326c49d471..caa5e7bb6f 100644 --- a/packages/nodes-base/nodes/SentryIo/GenericFunctions.ts +++ b/packages/nodes-base/nodes/SentryIo/GenericFunctions.ts @@ -11,7 +11,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function sentryIoApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -69,7 +69,7 @@ export async function sentryIoApiRequest(this: IHookFunctions | IExecuteFunction } } catch (error) { - throw new Error(`Sentry.io Error: ${error}`); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Shopify/GenericFunctions.ts b/packages/nodes-base/nodes/Shopify/GenericFunctions.ts index 9d366f5b0e..51c3fc543e 100644 --- a/packages/nodes-base/nodes/Shopify/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Shopify/GenericFunctions.ts @@ -11,7 +11,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import { @@ -21,7 +21,7 @@ import { export async function shopifyApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('shopifyApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const headerWithAuthentication = Object.assign({}, { Authorization: `Basic ${Buffer.from(`${credentials.apiKey}:${credentials.password}`).toString(BINARY_ENCODING)}` }); @@ -47,20 +47,7 @@ export async function shopifyApiRequest(this: IHookFunctions | IExecuteFunctions try { return await this.helpers.request!(options); } catch (error) { - if (error.response.body && error.response.body.errors) { - let message = ''; - if (typeof error.response.body.errors === 'object') { - for (const key of Object.keys(error.response.body.errors)) { - message += error.response.body.errors[key]; - } - } else { - message = `${error.response.body.errors} |`; - } - const errorMessage = `Shopify error response [${error.statusCode}]: ${message}`; - throw new Error(errorMessage); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Shopify/Shopify.node.ts b/packages/nodes-base/nodes/Shopify/Shopify.node.ts index 9ae04b7639..a289021f7f 100644 --- a/packages/nodes-base/nodes/Shopify/Shopify.node.ts +++ b/packages/nodes-base/nodes/Shopify/Shopify.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -139,7 +140,7 @@ export class Shopify implements INodeType { const shipping = additionalFields.shippingAddressUi as IDataObject; const lineItem = (this.getNodeParameter('limeItemsUi', i) as IDataObject).lineItemValues as IDataObject[]; if (lineItem === undefined) { - throw new Error('At least one line item has to be added'); + throw new NodeOperationError(this.getNode(), 'At least one line item has to be added'); } const body: IOrder = { test: true, diff --git a/packages/nodes-base/nodes/Shopify/ShopifyTrigger.node.ts b/packages/nodes-base/nodes/Shopify/ShopifyTrigger.node.ts index e6c49d4cf0..75d8f674aa 100644 --- a/packages/nodes-base/nodes/Shopify/ShopifyTrigger.node.ts +++ b/packages/nodes-base/nodes/Shopify/ShopifyTrigger.node.ts @@ -304,7 +304,7 @@ export class ShopifyTrigger implements INodeType { const webhookData = this.getWorkflowStaticData('node'); const webhookUrl = this.getNodeWebhookUrl('default'); const endpoint = `/webhooks`; - + const { webhooks } = await shopifyApiRequest.call(this, 'GET', endpoint, {}, { topic }); for (const webhook of webhooks) { if (webhook.address === webhookUrl) { @@ -328,7 +328,7 @@ export class ShopifyTrigger implements INodeType { }; let responseData; - + responseData = await shopifyApiRequest.call(this, 'POST', endpoint, body); if (responseData.webhook === undefined || responseData.webhook.id === undefined) { @@ -345,7 +345,7 @@ export class ShopifyTrigger implements INodeType { const endpoint = `/webhooks/${webhookData.webhookId}.json`; try { await shopifyApiRequest.call(this, 'DELETE', endpoint, {}); - } catch (e) { + } catch (error) { return false; } delete webhookData.webhookId; diff --git a/packages/nodes-base/nodes/Signl4/GenericFunctions.ts b/packages/nodes-base/nodes/Signl4/GenericFunctions.ts index 78e745e76c..0095542ab8 100644 --- a/packages/nodes-base/nodes/Signl4/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Signl4/GenericFunctions.ts @@ -3,7 +3,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; import { @@ -51,11 +51,6 @@ export async function SIGNL4ApiRequest(this: IExecuteFunctions, method: string, try { return await this.helpers.request!(options); } catch (error) { - - if (error.response && error.response.body && error.response.body.details) { - throw new Error(`SIGNL4 error response [${error.statusCode}]: ${error.response.body.details}`); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Signl4/Signl4.node.ts b/packages/nodes-base/nodes/Signl4/Signl4.node.ts index 1a729502ab..e54d7d0425 100644 --- a/packages/nodes-base/nodes/Signl4/Signl4.node.ts +++ b/packages/nodes-base/nodes/Signl4/Signl4.node.ts @@ -9,6 +9,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -315,7 +316,7 @@ export class Signl4 implements INodeType { if (!supportedFileExtension.includes(binaryProperty.fileExtension as string)) { - throw new Error(`Invalid extension, just ${supportedFileExtension.join(',')} are supported}`); + throw new NodeOperationError(this.getNode(), `Invalid extension, just ${supportedFileExtension.join(',')} are supported}`); } data.attachment = { @@ -327,7 +328,7 @@ export class Signl4 implements INodeType { }; } else { - throw new Error(`Binary property ${propertyName} does not exist on input`); + throw new NodeOperationError(this.getNode(), `Binary property ${propertyName} does not exist on input`); } } } diff --git a/packages/nodes-base/nodes/Slack/GenericFunctions.ts b/packages/nodes-base/nodes/Slack/GenericFunctions.ts index 43cc376b5c..70fbf195fd 100644 --- a/packages/nodes-base/nodes/Slack/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Slack/GenericFunctions.ts @@ -11,6 +11,8 @@ import { import { IDataObject, IOAuth2Options, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import * as _ from 'lodash'; @@ -40,7 +42,7 @@ export async function slackApiRequest(this: IExecuteFunctions | IExecuteSingleFu if (authenticationMethod === 'accessToken') { const credentials = this.getCredentials('slackApi'); 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}`; //@ts-ignore @@ -56,23 +58,12 @@ export async function slackApiRequest(this: IExecuteFunctions | IExecuteSingleFu } if (response.ok === false) { - throw new Error('Slack error response: ' + JSON.stringify(response)); + throw new NodeOperationError(this.getNode(), 'Slack error response: ' + JSON.stringify(response)); } return response; } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The Slack credentials are not valid!'); - } - - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - throw new Error(`Slack error response [${error.statusCode}]: ${error.response.body.message}`); - } - - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Slack/Slack.node.ts b/packages/nodes-base/nodes/Slack/Slack.node.ts index 132f3b339d..7a73e14075 100644 --- a/packages/nodes-base/nodes/Slack/Slack.node.ts +++ b/packages/nodes-base/nodes/Slack/Slack.node.ts @@ -10,6 +10,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -569,10 +570,10 @@ export class Slack implements INodeType { for (const elementUi of elementsUi) { const element: Element = {}; if (elementUi.actionId === '') { - throw new Error('Action ID must be set'); + throw new NodeOperationError(this.getNode(), 'Action ID must be set'); } if (elementUi.text === '') { - throw new Error('Text must be set'); + throw new NodeOperationError(this.getNode(), 'Text must be set'); } element.action_id = elementUi.actionId as string; element.type = elementUi.type as string; @@ -649,7 +650,7 @@ export class Slack implements INodeType { text.text = textUi.text as string; block.text = text; } else { - throw new Error('Property text must be defined'); + throw new NodeOperationError(this.getNode(), 'Property text must be defined'); } const fieldsUi = (blockUi.fieldsUi as IDataObject).fieldsValues as IDataObject[]; if (fieldsUi) { @@ -744,10 +745,10 @@ export class Slack implements INodeType { const attachmentsJson = this.getNodeParameter('attachmentsJson', i, '') as string; const blocksJson = this.getNodeParameter('blocksJson', i, []) as string; if (attachmentsJson !== '' && validateJSON(attachmentsJson) === undefined) { - throw new Error('Attachments it is not a valid json'); + throw new NodeOperationError(this.getNode(), 'Attachments it is not a valid json'); } if (blocksJson !== '' && validateJSON(blocksJson) === undefined) { - throw new Error('Blocks it is not a valid json'); + throw new NodeOperationError(this.getNode(), 'Blocks it is not a valid json'); } if (attachmentsJson !== '') { body.attachments = attachmentsJson; @@ -922,7 +923,7 @@ export class Slack implements INodeType { if (items[i].binary === undefined //@ts-ignore || 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!`); } body.file = { //@ts-ignore diff --git a/packages/nodes-base/nodes/Sms77/GenericFunctions.ts b/packages/nodes-base/nodes/Sms77/GenericFunctions.ts index 30499b3b29..aa104b6297 100644 --- a/packages/nodes-base/nodes/Sms77/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Sms77/GenericFunctions.ts @@ -6,6 +6,8 @@ import { import { ICredentialDataDecryptedObject, IDataObject, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; /** @@ -21,7 +23,7 @@ import { export async function sms77ApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, form: IDataObject, qs?: IDataObject): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('sms77Api'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } if ('GET' === method) { @@ -38,7 +40,7 @@ export async function sms77ApiRequest(this: IHookFunctions | IExecuteFunctions, }); if ('100' !== response.success) { - throw new Error('Invalid sms77 credentials or API error!'); + throw new NodeApiError(this.getNode(), response, { message: 'Invalid sms77 credentials or API error!' }); } return response; diff --git a/packages/nodes-base/nodes/Sms77/Sms77.node.ts b/packages/nodes-base/nodes/Sms77/Sms77.node.ts index 745563d715..75909cdc38 100644 --- a/packages/nodes-base/nodes/Sms77/Sms77.node.ts +++ b/packages/nodes-base/nodes/Sms77/Sms77.node.ts @@ -1,5 +1,5 @@ import {IExecuteFunctions,} from 'n8n-core'; -import {IDataObject, INodeExecutionData, INodeType, INodeTypeDescription,} from 'n8n-workflow'; +import {IDataObject, INodeExecutionData, INodeType, INodeTypeDescription, NodeOperationError,} from 'n8n-workflow'; import {sms77ApiRequest} from './GenericFunctions'; export class Sms77 implements INodeType { @@ -123,12 +123,12 @@ export class Sms77 implements INodeType { for (let i = 0; i < this.getInputData().length; i++) { const resource = this.getNodeParameter('resource', i); if ('sms' !== resource) { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } const operation = this.getNodeParameter('operation', i); if ('send' !== operation) { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } const responseData = await sms77ApiRequest.call(this, 'POST', 'sms', {}, { diff --git a/packages/nodes-base/nodes/Spontit/GenericFunctions.ts b/packages/nodes-base/nodes/Spontit/GenericFunctions.ts index cce7ccfc29..706b10d372 100644 --- a/packages/nodes-base/nodes/Spontit/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Spontit/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function spontitApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -35,15 +35,6 @@ export async function spontitApiRequest(this: IExecuteFunctions | ILoadOptionsFu //@ts-ignore return await this.helpers?.request(options); } catch (error) { - - if (error.response && error.response.body && error.response.body.message) { - - const messages = error.response.body.message; - // Try to return the error prettier - throw new Error( - `Spontit error response [${error.statusCode}]: ${messages}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Spotify/GenericFunctions.ts b/packages/nodes-base/nodes/Spotify/GenericFunctions.ts index 8c91c4957c..bce394c38a 100644 --- a/packages/nodes-base/nodes/Spotify/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Spotify/GenericFunctions.ts @@ -9,6 +9,8 @@ import { import { IDataObject, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; /** @@ -30,37 +32,19 @@ export async function spotifyApiRequest(this: IHookFunctions | IExecuteFunctions 'Content-Type': 'text/plain', 'Accept': ' application/json', }, - body, qs: query, uri: uri || `https://api.spotify.com/v1${endpoint}`, json: true, }; + if (Object.keys(body).length > 0) { + options.body = body; + } + try { - const credentials = this.getCredentials('spotifyOAuth2Api'); - - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - - if (Object.keys(body).length === 0) { - delete options.body; - } - return await this.helpers.requestOAuth2.call(this, 'spotifyOAuth2Api', options); } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The Spotify credentials are not valid!'); - } - - if (error.error && error.error.error && error.error.error.message) { - // Try to return the error prettier - throw new Error(`Spotify error response [${error.error.error.status}]: ${error.error.error.message}`); - } - - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/SpreadsheetFile.node.ts b/packages/nodes-base/nodes/SpreadsheetFile.node.ts index b7bb5faaeb..bbbc136c46 100644 --- a/packages/nodes-base/nodes/SpreadsheetFile.node.ts +++ b/packages/nodes-base/nodes/SpreadsheetFile.node.ts @@ -8,6 +8,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -334,13 +335,13 @@ export class SpreadsheetFile implements INodeType { } if (workbook.SheetNames.length === 0) { - throw new Error('Spreadsheet does not have any sheets!'); + throw new NodeOperationError(this.getNode(), 'Spreadsheet does not have any sheets!'); } let sheetName = workbook.SheetNames[0]; if (options.sheetName) { if (!workbook.SheetNames.includes(options.sheetName as string)) { - throw new Error(`Spreadsheet does not contain sheet called "${options.sheetName}"!`); + throw new NodeOperationError(this.getNode(), `Spreadsheet does not contain sheet called "${options.sheetName}"!`); } sheetName = options.sheetName as string; } @@ -442,7 +443,7 @@ export class SpreadsheetFile implements INodeType { return this.prepareOutputData(newItems); } else { - throw new Error(`The operation "${operation}" is not supported!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not supported!`); } } } diff --git a/packages/nodes-base/nodes/Stackby/GenericFunction.ts b/packages/nodes-base/nodes/Stackby/GenericFunction.ts index 05c11abdce..a39cb9be12 100644 --- a/packages/nodes-base/nodes/Stackby/GenericFunction.ts +++ b/packages/nodes-base/nodes/Stackby/GenericFunction.ts @@ -11,6 +11,7 @@ import { import { IDataObject, IPollFunctions, + NodeApiError, } from 'n8n-workflow'; /** @@ -49,20 +50,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa return await this.helpers.request!(options); } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The stackby credentials are not valid!'); - } - - if (error.response && error.response.body && error.response.body.error) { - // Try to return the error prettier - const message = error.response.body.error; - - throw new Error(`Stackby error response [${error.statusCode}]: ${message}`); - } - - // Expected error data did not get returned so rhow the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Stackby/Stackby.node.ts b/packages/nodes-base/nodes/Stackby/Stackby.node.ts index 8bf70d67a5..f06c6836de 100644 --- a/packages/nodes-base/nodes/Stackby/Stackby.node.ts +++ b/packages/nodes-base/nodes/Stackby/Stackby.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -230,7 +231,7 @@ export class Stackby implements INodeType { const record: { [key: string]: any } = {}; for (const column of columnList) { if (items[i].json[column] === undefined) { - throw new Error(`Column ${column} does not exist on input`); + throw new NodeOperationError(this.getNode(), `Column ${column} does not exist on input`); } else { record[column] = items[i].json[column]; } diff --git a/packages/nodes-base/nodes/Storyblok/GenericFunctions.ts b/packages/nodes-base/nodes/Storyblok/GenericFunctions.ts index 71ff1857b9..7ed4e462da 100644 --- a/packages/nodes-base/nodes/Storyblok/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Storyblok/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function storyblokApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -50,14 +50,7 @@ export async function storyblokApiRequest(this: IHookFunctions | IExecuteFunctio try { return this.helpers.request!(options); } catch (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(`Storyblok error response [${error.statusCode}]: ${errorBody.message}`); - } - - // Expected error data did not get returned so throw the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Strapi/GenericFunctions.ts b/packages/nodes-base/nodes/Strapi/GenericFunctions.ts index fbd7c57ff0..1164d6b021 100644 --- a/packages/nodes-base/nodes/Strapi/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Strapi/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function strapiApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -36,19 +36,7 @@ export async function strapiApiRequest(this: IExecuteFunctions | ILoadOptionsFun //@ts-ignore return await this.helpers?.request(options); } catch (error) { - if (error.response && error.response.body && error.response.body.message) { - - let messages = error.response.body.message; - - if (Array.isArray(error.response.body.message)) { - messages = messages[0].messages.map((e: IDataObject) => e.message).join('|'); - } - // Try to return the error prettier - throw new Error( - `Strapi error response [${error.statusCode}]: ${messages}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Strapi/Strapi.node.ts b/packages/nodes-base/nodes/Strapi/Strapi.node.ts index 74849e6187..70e3357632 100644 --- a/packages/nodes-base/nodes/Strapi/Strapi.node.ts +++ b/packages/nodes-base/nodes/Strapi/Strapi.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -129,7 +130,7 @@ export class Strapi implements INodeType { if (query !== undefined) { qs._where = query; } else { - throw new Error('Query must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Query must be a valid JSON'); } } diff --git a/packages/nodes-base/nodes/Strava/GenericFunctions.ts b/packages/nodes-base/nodes/Strava/GenericFunctions.ts index 8f21ac7f82..f854b7ad3d 100644 --- a/packages/nodes-base/nodes/Strava/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Strava/GenericFunctions.ts @@ -11,7 +11,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function stravaApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -48,24 +48,7 @@ export async function stravaApiRequest(this: IExecuteFunctions | IExecuteSingleF return await this.helpers.requestOAuth2.call(this, 'stravaOAuth2Api', options, { includeCredentialsOnRefreshOnBody: true }); } } catch (error) { - - if (error.statusCode === 402) { - throw new Error( - `Strava error response [${error.statusCode}]: Payment Required`, - ); - } - - if (error.response && error.response.body && error.response.body.errors) { - - let errors = error.response.body.errors; - - errors = errors.map((e: IDataObject) => `${e.code} -> ${e.field}`); - // Try to return the error prettier - throw new Error( - `Strava error response [${error.statusCode}]: ${errors.join('|')}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Strava/StravaTrigger.node.ts b/packages/nodes-base/nodes/Strava/StravaTrigger.node.ts index 34d66724c8..0e78a63555 100644 --- a/packages/nodes-base/nodes/Strava/StravaTrigger.node.ts +++ b/packages/nodes-base/nodes/Strava/StravaTrigger.node.ts @@ -8,6 +8,7 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeApiError, } from 'n8n-workflow'; import { @@ -161,8 +162,9 @@ export class StravaTrigger implements INodeType { try { responseData = await stravaApiRequest.call(this, 'POST', endpoint, body); } catch (error) { - if (error.response && error.response.body && error.response.body.errors) { - const errors = error.response.body.errors; + const apiErrorResponse = error.cause.response; + if (apiErrorResponse?.body?.errors) { + const errors = apiErrorResponse.body.errors; for (error of errors) { // if there is a subscription already created if (error.resource === 'PushSubscription' && error.code === 'already exists') { @@ -181,24 +183,15 @@ export class StravaTrigger implements INodeType { responseData = await stravaApiRequest.call(this, 'POST', `/push_subscriptions`, body); } else { - throw new Error(`A subscription already exist [${webhooks[0].callback_url}]. - If you want to delete this subcription and create a new one with the current parameters please go to options and set delete if exist to true`); + error.message = `A subscription already exists [${webhooks[0].callback_url}]. If you want to delete this subcription and create a new one with the current parameters please go to options and set delete if exist to true`; + throw error; } } } } if (!responseData) { - let errorMessage = ''; - if (error.response && error.response.body && error.response.body.message) { - errorMessage = error.response.body.message; - } else { - errorMessage = error.message; - } - - throw new Error( - `Strava error response [${error.statusCode}]: ${errorMessage}`, - ); + throw error; } } @@ -218,7 +211,7 @@ export class StravaTrigger implements INodeType { try { await stravaApiRequest.call(this, 'DELETE', endpoint); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts b/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts index 08e9a0e9ba..f29208c0ee 100644 --- a/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts +++ b/packages/nodes-base/nodes/Stripe/StripeTrigger.node.ts @@ -8,6 +8,8 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -817,8 +819,8 @@ export class StripeTrigger implements INodeType { try { await stripeApiRequest.call(this, 'GET', endpoint, {}); - } catch (e) { - if (e.message.includes('resource_missing')) { + } catch (error) { + if (error.message.includes('resource_missing')) { // Webhook does not exist delete webhookData.webhookId; delete webhookData.webhookEvents; @@ -828,7 +830,7 @@ export class StripeTrigger implements INodeType { } // Some error occured - throw e; + throw error; } // If it did not error then the webhook exists @@ -849,13 +851,13 @@ export class StripeTrigger implements INodeType { let responseData; try { responseData = await stripeApiRequest.call(this, 'POST', endpoint, body); - } catch (e) { - throw e; + } catch (error) { + throw error; } if (responseData.id === undefined || responseData.secret === undefined || responseData.status !== 'enabled') { // Required data is missing so was not successful - throw new Error('Stripe webhook creation response did not contain the expected data.'); + throw new NodeApiError(this.getNode(), responseData, { message: 'Stripe webhook creation response did not contain the expected data.' }); } const webhookData = this.getWorkflowStaticData('node'); @@ -874,7 +876,7 @@ export class StripeTrigger implements INodeType { try { await stripeApiRequest.call(this, 'DELETE', endpoint, body); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/Stripe/helpers.ts b/packages/nodes-base/nodes/Stripe/helpers.ts index f2c05eede5..0fb8ebdb97 100644 --- a/packages/nodes-base/nodes/Stripe/helpers.ts +++ b/packages/nodes-base/nodes/Stripe/helpers.ts @@ -2,6 +2,7 @@ import { IExecuteFunctions, IHookFunctions, } from 'n8n-core'; +import { NodeApiError, NodeOperationError, } from 'n8n-workflow'; /** * Make an API request to Stripe @@ -15,7 +16,7 @@ import { export async function stripeApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: object, query?: object): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('stripeApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const options = { @@ -32,17 +33,6 @@ export async function stripeApiRequest(this: IHookFunctions | IExecuteFunctions, try { return await this.helpers.request(options); } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The Stripe credentials are not valid!'); - } - - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - throw new Error(`Stripe error response [${error.statusCode}]: ${error.response.body.message}`); - } - - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/SurveyMonkey/GenericFunctions.ts b/packages/nodes-base/nodes/SurveyMonkey/GenericFunctions.ts index 2c31a182d7..d09ba54b4c 100644 --- a/packages/nodes-base/nodes/SurveyMonkey/GenericFunctions.ts +++ b/packages/nodes-base/nodes/SurveyMonkey/GenericFunctions.ts @@ -10,7 +10,9 @@ import { import { IDataObject, IHookFunctions, - IWebhookFunctions + IWebhookFunctions, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; export async function surveyMonkeyApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -42,7 +44,7 @@ export async function surveyMonkeyApiRequest(this: IExecuteFunctions | IWebhookF const credentials = this.getCredentials('surveyMonkeyApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } // @ts-ignore options.headers['Authorization'] = `bearer ${credentials.accessToken}`; @@ -53,11 +55,7 @@ export async function surveyMonkeyApiRequest(this: IExecuteFunctions | IWebhookF return await this.helpers.requestOAuth2?.call(this, 'surveyMonkeyOAuth2Api', options); } } catch (error) { - const errorMessage = error.response.body.error.message; - if (errorMessage !== undefined) { - throw new Error(`SurveyMonkey error response [${error.statusCode}]: ${errorMessage}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts b/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts index 0c14549d9b..72d030d3bf 100644 --- a/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts +++ b/packages/nodes-base/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.ts @@ -11,6 +11,8 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -471,7 +473,7 @@ export class SurveyMonkeyTrigger implements INodeType { try { await surveyMonkeyApiRequest.call(this, 'DELETE', endpoint); - } catch (e) { + } catch (error) { return false; } @@ -737,8 +739,8 @@ export class SurveyMonkeyTrigger implements INodeType { }); }); - req.on('error', (err) => { - throw new Error(err.message); + req.on('error', (error) => { + throw new NodeOperationError(this.getNode(), error); }); }); } diff --git a/packages/nodes-base/nodes/Switch.node.ts b/packages/nodes-base/nodes/Switch.node.ts index fe6213eb0d..9473fa8002 100644 --- a/packages/nodes-base/nodes/Switch.node.ts +++ b/packages/nodes-base/nodes/Switch.node.ts @@ -4,6 +4,7 @@ import { INodeParameters, INodeType, INodeTypeDescription, + NodeOperationError, NodeParameterValue, } from 'n8n-workflow'; @@ -594,7 +595,7 @@ export class Switch implements INodeType { }; // Converts the input data of a dateTime into a number for easy compare - function convertDateTime(value: NodeParameterValue): number { + const convertDateTime = (value: NodeParameterValue): number => { let returnValue: number | undefined = undefined; if (typeof value === 'string') { returnValue = new Date(value).getTime(); @@ -605,17 +606,17 @@ export class Switch implements INodeType { } if (returnValue === undefined || isNaN(returnValue)) { - throw new Error(`The value "${value}" is not a valid DateTime.`); + throw new NodeOperationError(this.getNode(), `The value "${value}" is not a valid DateTime.`); } return returnValue; - } + }; - function checkIndexRange(index: number) { + const checkIndexRange = (index: number) => { if (index < 0 || index >= returnData.length) { - throw new Error(`The ouput ${index} is not allowed. It has to be between 0 and ${returnData.length - 1}!`); + throw new NodeOperationError(this.getNode(), `The ouput ${index} is not allowed. It has to be between 0 and ${returnData.length - 1}!`); } - } + }; const dataType = this.getNodeParameter('dataType', 0) as string; diff --git a/packages/nodes-base/nodes/Taiga/GenericFunctions.ts b/packages/nodes-base/nodes/Taiga/GenericFunctions.ts index ce5fcde5b8..2941bba021 100644 --- a/packages/nodes-base/nodes/Taiga/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Taiga/GenericFunctions.ts @@ -13,6 +13,8 @@ import { import { ICredentialDataDecryptedObject, IDataObject, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -24,7 +26,7 @@ export async function getAuthorization( credentials?: ICredentialDataDecryptedObject, ): Promise { if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const { password, username } = credentials; @@ -45,7 +47,7 @@ export async function getAuthorization( return response.auth_token; } catch (error) { - throw new Error('Taiga Error: ' + error.err || error); + throw new NodeApiError(this.getNode(), error); } } @@ -92,12 +94,7 @@ export async function taigaApiRequest( try { return await this.helpers.request!(options); } catch (error) { - let errorMessage = error; - if (error.response.body && error.response.body._error_message) { - errorMessage = error.response.body._error_message; - } - - throw new Error(`Taigan error response [${error.statusCode}]: ${errorMessage}`); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Tapfiliate/GenericFunctions.ts b/packages/nodes-base/nodes/Tapfiliate/GenericFunctions.ts index 2eea6eac51..bcceb04d86 100644 --- a/packages/nodes-base/nodes/Tapfiliate/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Tapfiliate/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function tapfiliateApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, qs: IDataObject = {}, uri?: string | undefined, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -37,23 +37,7 @@ export async function tapfiliateApiRequest(this: IHookFunctions | IExecuteFuncti try { return await this.helpers.request!(options); } catch (error) { - if (error.statusCode === 404) { - throw new Error( - `Tapfiliate error response [${error.statusCode}]: Not Found`, - ); - } - - if (error.response && error.response.body && error.response.body.errors) { - - let errors = error.response.body.errors; - - errors = errors.map((e: IDataObject) => e.message); - // Try to return the error prettier - throw new Error( - `Tapfiliate error response [${error.statusCode}]: ${errors.join('|')}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Tapfiliate/Tapfiliate.node.ts b/packages/nodes-base/nodes/Tapfiliate/Tapfiliate.node.ts index ef25358307..43d5fc574d 100644 --- a/packages/nodes-base/nodes/Tapfiliate/Tapfiliate.node.ts +++ b/packages/nodes-base/nodes/Tapfiliate/Tapfiliate.node.ts @@ -8,7 +8,8 @@ import { INodeExecutionData, INodePropertyOptions, INodeType, - INodeTypeDescription + INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -184,7 +185,7 @@ export class Tapfiliate implements INodeType { const affiliateId = this.getNodeParameter('affiliateId', i) as string; const metadata = (this.getNodeParameter('metadataUi', i) as IDataObject || {}).metadataValues as IDataObject[] || []; if (metadata.length === 0) { - throw new Error('Metadata cannot be empty.'); + throw new NodeOperationError(this.getNode(), 'Metadata cannot be empty.'); } for (const { key, value } of metadata) { await tapfiliateApiRequest.call(this, 'PUT', `/affiliates/${affiliateId}/meta-data/${key}/`, { value }); diff --git a/packages/nodes-base/nodes/Telegram/GenericFunctions.ts b/packages/nodes-base/nodes/Telegram/GenericFunctions.ts index f59c8826bc..b5df173bf1 100644 --- a/packages/nodes-base/nodes/Telegram/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Telegram/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'request'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; // Interface in n8n @@ -146,7 +146,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa const credentials = this.getCredentials('telegramApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } query = query || {}; @@ -176,20 +176,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa try { return await this.helpers.request!(options); } catch (error) { - - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The Telegram 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(`Telegram error response [${errorBody.error_code}]: ${errorBody.description}`); - } - - // Expected error data did not get returned so throw the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Telegram/Telegram.node.ts b/packages/nodes-base/nodes/Telegram/Telegram.node.ts index e22d791c70..e55c477419 100644 --- a/packages/nodes-base/nodes/Telegram/Telegram.node.ts +++ b/packages/nodes-base/nodes/Telegram/Telegram.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -2013,7 +2014,7 @@ export class Telegram implements INodeType { } } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); @@ -2025,7 +2026,7 @@ export class Telegram implements INodeType { const credentials = this.getCredentials('telegramApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const file = await apiRequest.call(this, 'GET', '', {}, {}, { json: false, encoding: null, uri: `https://api.telegram.org/file/bot${credentials.accessToken}/${filePath}`, resolveWithFullResponse: true }); diff --git a/packages/nodes-base/nodes/Telegram/TelegramTrigger.node.ts b/packages/nodes-base/nodes/Telegram/TelegramTrigger.node.ts index 69fab1d2ac..b55b4264e0 100644 --- a/packages/nodes-base/nodes/Telegram/TelegramTrigger.node.ts +++ b/packages/nodes-base/nodes/Telegram/TelegramTrigger.node.ts @@ -199,7 +199,7 @@ export class TelegramTrigger implements INodeType { try { await apiRequest.call(this, 'POST', endpoint, body); - } catch (e) { + } catch (error) { return false; } diff --git a/packages/nodes-base/nodes/TheHive/GenericFunctions.ts b/packages/nodes-base/nodes/TheHive/GenericFunctions.ts index 4a64dd36bb..18d22ec1df 100644 --- a/packages/nodes-base/nodes/TheHive/GenericFunctions.ts +++ b/packages/nodes-base/nodes/TheHive/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import * as moment from 'moment'; @@ -18,7 +18,7 @@ export async function theHiveApiRequest(this: IHookFunctions | IExecuteFunctions const credentials = this.getCredentials('theHiveApi'); 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.ApiKey}` }); @@ -47,10 +47,7 @@ export async function theHiveApiRequest(this: IHookFunctions | IExecuteFunctions try { return await this.helpers.request!(options); } catch (error) { - if (error.error) { - const errorMessage = `TheHive error response [${error.statusCode}]: ${error.error.message || error.error.type}`; - throw new Error(errorMessage); - } else throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/TheHive/TheHive.node.ts b/packages/nodes-base/nodes/TheHive/TheHive.node.ts index dc40862a62..e3021879c0 100644 --- a/packages/nodes-base/nodes/TheHive/TheHive.node.ts +++ b/packages/nodes-base/nodes/TheHive/TheHive.node.ts @@ -11,7 +11,8 @@ import { INodeParameters, INodePropertyOptions, INodeType, - INodeTypeDescription + INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -389,13 +390,13 @@ export class TheHive implements INodeType { const item = items[i]; 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; 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; @@ -666,13 +667,13 @@ export class TheHive implements INodeType { const item = items[i]; 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; 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; @@ -868,13 +869,13 @@ export class TheHive implements INodeType { const item = items[i]; 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('binaryProperty', i) as string; 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; @@ -1801,13 +1802,13 @@ export class TheHive implements INodeType { const item = items[i]; 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 = attachmentValues.binaryProperty as string; 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; diff --git a/packages/nodes-base/nodes/TimescaleDb/TimescaleDb.node.ts b/packages/nodes-base/nodes/TimescaleDb/TimescaleDb.node.ts index 54dbc069e6..72bf645efe 100644 --- a/packages/nodes-base/nodes/TimescaleDb/TimescaleDb.node.ts +++ b/packages/nodes-base/nodes/TimescaleDb/TimescaleDb.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -223,7 +224,7 @@ export class TimescaleDb implements INodeType { const credentials = this.getCredentials('timescaleDb'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const pgp = pgPromise(); @@ -279,7 +280,7 @@ export class TimescaleDb implements INodeType { } else { 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 diff --git a/packages/nodes-base/nodes/Todoist/GenericFunctions.ts b/packages/nodes-base/nodes/Todoist/GenericFunctions.ts index 31d8d85f99..a6f12e148d 100644 --- a/packages/nodes-base/nodes/Todoist/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Todoist/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function todoistApiRequest( @@ -52,12 +52,6 @@ export async function todoistApiRequest( } } catch (error) { - const errorMessage = error.response.body; - - if (errorMessage !== undefined) { - throw new Error(errorMessage); - } - - throw errorMessage; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Toggl/GenericFunctions.ts b/packages/nodes-base/nodes/Toggl/GenericFunctions.ts index 534606adf6..8c4fd11306 100644 --- a/packages/nodes-base/nodes/Toggl/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Toggl/GenericFunctions.ts @@ -10,13 +10,13 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function togglApiRequest(this: ITriggerFunctions | IPollFunctions | IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('togglApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const headerWithAuthentication = Object.assign({}, { Authorization: ` Basic ${Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64')}` }); @@ -35,15 +35,6 @@ export async function togglApiRequest(this: ITriggerFunctions | IPollFunctions | try { return await this.helpers.request!(options); } catch (error) { - if (error.statusCode === 403) { - throw new Error('The Toggle credentials are probably invalid!'); - } - - const errorMessage = error.response.body && (error.response.body.message || error.response.body.Message); - if (errorMessage !== undefined) { - throw new Error(errorMessage); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Toggl/TogglTrigger.node.ts b/packages/nodes-base/nodes/Toggl/TogglTrigger.node.ts index 2f92422157..c76e783351 100644 --- a/packages/nodes-base/nodes/Toggl/TogglTrigger.node.ts +++ b/packages/nodes-base/nodes/Toggl/TogglTrigger.node.ts @@ -4,6 +4,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import * as moment from 'moment'; @@ -55,7 +57,7 @@ export class TogglTrigger implements INodeType { if (event === 'newTimeEntry') { endpoint = '/time_entries'; } else { - throw new Error(`The defined event "${event}" is not supported`); + throw new NodeOperationError(this.getNode(), `The defined event "${event}" is not supported`); } const qs: IDataObject = {}; @@ -66,8 +68,8 @@ export class TogglTrigger implements INodeType { try { timeEntries = await togglApiRequest.call(this, 'GET', endpoint, {}, qs); webhookData.lastTimeChecked = qs.end_date; - } catch (err) { - throw new Error(`Toggl Trigger Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } if (Array.isArray(timeEntries) && timeEntries.length !== 0) { return [this.helpers.returnJsonArray(timeEntries)]; diff --git a/packages/nodes-base/nodes/TravisCi/GenericFunctions.ts b/packages/nodes-base/nodes/TravisCi/GenericFunctions.ts index 4ae3604995..2451eb9ed6 100644 --- a/packages/nodes-base/nodes/TravisCi/GenericFunctions.ts +++ b/packages/nodes-base/nodes/TravisCi/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import { @@ -22,7 +22,7 @@ import * as querystring from 'querystring'; export async function travisciApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('travisCiApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { headers: { @@ -43,14 +43,8 @@ export async function travisciApiRequest(this: IHookFunctions | IExecuteFunction } try { return await this.helpers.request!(options); - } catch (err) { - if (err.response && err.response.body && err.response.body.error_message) { - // Try to return the error prettier - throw new Error(`TravisCI error response [${err.statusCode}]: ${err.response.body.error_message}`); - } - - // If that data does not exist for some reason return the actual error - throw err; + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Trello/GenericFunctions.ts b/packages/nodes-base/nodes/Trello/GenericFunctions.ts index c36e1aef0b..a90d1e88f1 100644 --- a/packages/nodes-base/nodes/Trello/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Trello/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'request'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; /** @@ -25,7 +25,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa const credentials = this.getCredentials('trelloApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } query = query || {}; @@ -46,11 +46,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa try { return await this.helpers.request!(options); } catch (error) { - if (error.statusCode === 401) { - throw new Error('The Trello credentials are not valid!'); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Trello/Trello.node.ts b/packages/nodes-base/nodes/Trello/Trello.node.ts index 6781dbcca8..e40f8cec19 100644 --- a/packages/nodes-base/nodes/Trello/Trello.node.ts +++ b/packages/nodes-base/nodes/Trello/Trello.node.ts @@ -7,6 +7,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -213,7 +214,7 @@ export class Trello implements INodeType { Object.assign(qs, updateFields); } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else if (resource === 'card') { @@ -274,7 +275,7 @@ export class Trello implements INodeType { Object.assign(qs, updateFields); } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else if (resource === 'cardComment') { @@ -322,7 +323,7 @@ export class Trello implements INodeType { endpoint = `cards/${cardId}/actions/${commentId}/comments`; } 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') { @@ -423,7 +424,7 @@ export class Trello implements INodeType { Object.assign(qs, updateFields); } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else if (resource === 'attachment') { @@ -488,7 +489,7 @@ export class Trello implements INodeType { const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; Object.assign(qs, additionalFields); } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else if (resource === 'checklist') { @@ -622,7 +623,7 @@ export class Trello implements INodeType { Object.assign(qs, additionalFields); } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else if (resource === 'label') { @@ -725,10 +726,10 @@ export class Trello implements INodeType { endpoint = `/cards/${cardId}/idLabels/${id}`; } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } diff --git a/packages/nodes-base/nodes/Trello/TrelloTrigger.node.ts b/packages/nodes-base/nodes/Trello/TrelloTrigger.node.ts index b756d0aa34..a98bb570ab 100644 --- a/packages/nodes-base/nodes/Trello/TrelloTrigger.node.ts +++ b/packages/nodes-base/nodes/Trello/TrelloTrigger.node.ts @@ -7,6 +7,7 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeOperationError, } from 'n8n-workflow'; import { @@ -71,7 +72,7 @@ export class TrelloTrigger implements INodeType { const credentials = this.getCredentials('trelloApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } // Check all the webhooks which exist already if it is identical to the @@ -99,7 +100,7 @@ export class TrelloTrigger implements INodeType { const credentials = this.getCredentials('trelloApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const idModel = this.getNodeParameter('id') as string; @@ -130,7 +131,7 @@ export class TrelloTrigger implements INodeType { if (webhookData.webhookId !== undefined) { const credentials = this.getCredentials('trelloApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const endpoint = `tokens/${credentials.apiToken}/webhooks/${webhookData.webhookId}`; @@ -139,7 +140,7 @@ export class TrelloTrigger implements INodeType { try { await apiRequest.call(this, 'DELETE', endpoint, body); - } catch (e) { + } catch (error) { return false; } @@ -172,7 +173,7 @@ export class TrelloTrigger implements INodeType { const credentials = this.getCredentials('trelloApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } // TODO: Check why that does not work as expected even though it gets done as described diff --git a/packages/nodes-base/nodes/Twake/GenericFunctions.ts b/packages/nodes-base/nodes/Twake/GenericFunctions.ts index d756c9cf2b..141d7493e4 100644 --- a/packages/nodes-base/nodes/Twake/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Twake/GenericFunctions.ts @@ -3,6 +3,7 @@ import { IHookFunctions, ILoadOptionsFunctions, } from 'n8n-core'; +import { NodeApiError, NodeOperationError, } from 'n8n-workflow'; import { OptionsWithUri, @@ -46,23 +47,8 @@ export async function twakeApiRequest(this: IHookFunctions | IExecuteFunctions | return await this.helpers.request!(options); } catch (error) { if (error.error.code === 'ECONNREFUSED') { - throw new Error('Twake host is not accessible!'); - + throw new NodeApiError(this.getNode(), error, { message: 'Twake host is not accessible!' }); } - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The Twake credentials are not valid!'); - } - - 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(`Twake error response [${error.statusCode}]: ${errorMessages.join(' | ')}`); - } - - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Twake/Twake.node.ts b/packages/nodes-base/nodes/Twake/Twake.node.ts index 7d1da31823..f9ef73ba34 100644 --- a/packages/nodes-base/nodes/Twake/Twake.node.ts +++ b/packages/nodes-base/nodes/Twake/Twake.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -176,7 +177,7 @@ export class Twake implements INodeType { async getChannels(this: ILoadOptionsFunctions): Promise { const responseData = await twakeApiRequest.call(this, 'POST', '/channel', {}); if (responseData === undefined) { - throw new Error('No data got returned'); + throw new NodeOperationError(this.getNode(), 'No data got returned'); } const returnData: INodePropertyOptions[] = []; diff --git a/packages/nodes-base/nodes/Twilio/GenericFunctions.ts b/packages/nodes-base/nodes/Twilio/GenericFunctions.ts index fba00a7e3d..e5eaf606e0 100644 --- a/packages/nodes-base/nodes/Twilio/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Twilio/GenericFunctions.ts @@ -4,7 +4,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; /** @@ -19,7 +19,7 @@ import { export async function twilioApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: IDataObject, query?: IDataObject): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('twilioApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } if (query === undefined) { @@ -41,22 +41,6 @@ export async function twilioApiRequest(this: IHookFunctions | IExecuteFunctions, try { return await this.helpers.request(options); } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The Twilio credentials are not valid!'); - } - - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - let errorMessage = `Twilio error response [${error.statusCode}]: ${error.response.body.message}`; - if (error.response.body.more_info) { - errorMessage = `errorMessage (${error.response.body.more_info})`; - } - - throw new Error(errorMessage); - } - - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Twilio/Twilio.node.ts b/packages/nodes-base/nodes/Twilio/Twilio.node.ts index f227c66687..3b3dd135ad 100644 --- a/packages/nodes-base/nodes/Twilio/Twilio.node.ts +++ b/packages/nodes-base/nodes/Twilio/Twilio.node.ts @@ -6,6 +6,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -199,10 +200,10 @@ export class Twilio implements INodeType { body.To = `whatsapp:${body.To}`; } } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else { - throw new Error(`The resource "${resource}" is not known!`); + throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`); } const responseData = await twilioApiRequest.call(this, requestMethod, endpoint, body, qs); diff --git a/packages/nodes-base/nodes/Twist/GenericFunctions.ts b/packages/nodes-base/nodes/Twist/GenericFunctions.ts index 8a633ea5f0..cece7d263c 100644 --- a/packages/nodes-base/nodes/Twist/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Twist/GenericFunctions.ts @@ -8,7 +8,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function twistApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, qs: IDataObject = {}, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -36,15 +36,6 @@ export async function twistApiRequest(this: IExecuteFunctions | ILoadOptionsFunc return await this.helpers.requestOAuth2.call(this, 'twistOAuth2Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.error_string) { - - const message = error.response.body.error_string; - - // Try to return the error prettier - throw new Error( - `Twist error response [${error.statusCode}]: ${message}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Twist/Twist.node.ts b/packages/nodes-base/nodes/Twist/Twist.node.ts index f46017176d..d2fe9f18dd 100644 --- a/packages/nodes-base/nodes/Twist/Twist.node.ts +++ b/packages/nodes-base/nodes/Twist/Twist.node.ts @@ -12,6 +12,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -237,7 +238,7 @@ export class Twist implements INodeType { const binaryData = item[binaryProperty] as IBinaryData; if (binaryData === undefined) { - throw new Error(`No binary data property "${binaryProperty}" does not exists on item!`); + throw new NodeOperationError(this.getNode(), `No binary data property "${binaryProperty}" does not exists on item!`); } attachments.push(await twistApiRequest.call( diff --git a/packages/nodes-base/nodes/Twitter/GenericFunctions.ts b/packages/nodes-base/nodes/Twitter/GenericFunctions.ts index 71d0aabeac..260f2ac3ca 100644 --- a/packages/nodes-base/nodes/Twitter/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Twitter/GenericFunctions.ts @@ -14,6 +14,8 @@ import { IBinaryKeyData, IDataObject, INodeExecutionData, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; export async function twitterApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -37,15 +39,7 @@ export async function twitterApiRequest(this: IExecuteFunctions | IExecuteSingle //@ts-ignore return await this.helpers.requestOAuth1.call(this, 'twitterOAuth1Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.errors) { - // Try to return the error prettier - const errorMessages = error.response.body.errors.map((error: IDataObject) => { - return error.message; - }); - throw new Error(`Twitter error response [${error.statusCode}]: ${errorMessages.join(' | ')}`); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } @@ -92,7 +86,7 @@ export async function uploadAttachments(this: IExecuteFunctions, binaryPropertie const binaryData = items[i].binary as IBinaryKeyData; if (binaryData === undefined) { - throw new Error('No binary data set. So file can not be written!'); + throw new NodeOperationError(this.getNode(), 'No binary data set. So file can not be written!'); } if (!binaryData[binaryPropertyName]) { @@ -107,7 +101,7 @@ export async function uploadAttachments(this: IExecuteFunctions, binaryPropertie const isImage = binaryData[binaryPropertyName].mimeType.includes('image'); if (isImage && isAnimatedWebp) { - throw new Error('Animated .webp images are not supported use .gif instead'); + throw new NodeOperationError(this.getNode(), 'Animated .webp images are not supported use .gif instead'); } if (isImage) { diff --git a/packages/nodes-base/nodes/Typeform/GenericFunctions.ts b/packages/nodes-base/nodes/Typeform/GenericFunctions.ts index 11110ca4f1..c19c2ec2d8 100644 --- a/packages/nodes-base/nodes/Typeform/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Typeform/GenericFunctions.ts @@ -5,7 +5,7 @@ import { } from 'n8n-core'; import { - INodePropertyOptions, + INodePropertyOptions, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import { @@ -68,7 +68,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa const credentials = this.getCredentials('typeformApi'); 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}`; @@ -78,19 +78,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa return await this.helpers.requestOAuth2!.call(this, 'typeformOAuth2Api', options); } } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The Typeform credentials are not valid!'); - } - - if (error.response && error.response.body && error.response.body.description) { - // Try to return the error prettier - const errorBody = error.response.body; - throw new Error(`Typeform error response [${error.statusCode} - errorBody.code]: ${errorBody.description}`); - } - - // Expected error data did not get returned so throw the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } @@ -149,7 +137,7 @@ export async function getForms(this: ILoadOptionsFunctions): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('uprocApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const token = Buffer.from(`${credentials.email}:${credentials.apiKey}`).toString('base64'); const options: OptionsWithUri = { @@ -34,17 +34,6 @@ export async function uprocApiRequest(this: IHookFunctions | IExecuteFunctions | try { return await this.helpers.request!(options); } catch (error) { - if (error.statusCode === 403) { - // Return a clear error - throw new Error('The uProc 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(`uProc Error [${error.statusCode}]: ${error.response.body.error.message}`); - } - - // If that data does not exist for some reason return the actual error - throw new Error('uProc Error: ' + error.message); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/UnleashedSoftware/GenericFunctions.ts b/packages/nodes-base/nodes/UnleashedSoftware/GenericFunctions.ts index 2689b8dab9..d6278bd57e 100644 --- a/packages/nodes-base/nodes/UnleashedSoftware/GenericFunctions.ts +++ b/packages/nodes-base/nodes/UnleashedSoftware/GenericFunctions.ts @@ -11,7 +11,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; import { @@ -43,7 +43,7 @@ export async function unleashedApiRequest(this: IHookFunctions | IExecuteFunctio const credentials = this.getCredentials('unleashedSoftwareApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const signature = createHmac('sha256', (credentials.apiKey as string)) @@ -58,13 +58,10 @@ export async function unleashedApiRequest(this: IHookFunctions | IExecuteFunctio try { return await this.helpers.request!(options); } catch (error) { - if (error.response && error.response.body && error.response.body.description) { - throw new Error(`Unleashed Error response [${error.statusCode}]: ${error.response.body.description}`); - } - - throw error; + throw new NodeApiError(this.getNode(), error); } } + export async function unleashedApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any const returnData: IDataObject[] = []; diff --git a/packages/nodes-base/nodes/Uplead/GenericFunctions.ts b/packages/nodes-base/nodes/Uplead/GenericFunctions.ts index 176996342f..668c78d36a 100644 --- a/packages/nodes-base/nodes/Uplead/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Uplead/GenericFunctions.ts @@ -5,12 +5,12 @@ import { IHookFunctions, ILoadOptionsFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function upleadApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('upleadApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { headers: { Authorization: credentials.apiKey }, @@ -26,7 +26,7 @@ export async function upleadApiRequest(this: IHookFunctions | IExecuteFunctions } try { return await this.helpers.request!(options); - } catch (err) { - throw new Error(err); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Vero/GenericFunctions.ts b/packages/nodes-base/nodes/Vero/GenericFunctions.ts index dd3a7fcab1..faeded0600 100644 --- a/packages/nodes-base/nodes/Vero/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Vero/GenericFunctions.ts @@ -4,12 +4,12 @@ import { IExecuteSingleFunctions, ILoadOptionsFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function veroApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('veroApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { @@ -30,12 +30,7 @@ export async function veroApiRequest(this: IExecuteFunctions | IExecuteSingleFun try { return await this.helpers.request!(options); } catch (error) { - let errorMessage = error.message; - if (error.response.body) { - errorMessage = error.response.body.message || error.response.body.Message || error.message; - } - - throw new Error(errorMessage); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Vero/Vero.node.ts b/packages/nodes-base/nodes/Vero/Vero.node.ts index 6eb28aa765..d7fd01cb08 100644 --- a/packages/nodes-base/nodes/Vero/Vero.node.ts +++ b/packages/nodes-base/nodes/Vero/Vero.node.ts @@ -6,6 +6,8 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { validateJSON, @@ -110,8 +112,8 @@ export class Vero implements INodeType { } try { responseData = await veroApiRequest.call(this, 'POST', '/users/track', body); - } catch (err) { - throw new Error(`Vero Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developers.getvero.com/?bash#users-alias @@ -124,8 +126,8 @@ export class Vero implements INodeType { }; try { responseData = await veroApiRequest.call(this, 'PUT', '/users/reidentify', body); - } catch (err) { - throw new Error(`Vero Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developers.getvero.com/?bash#users-unsubscribe @@ -140,8 +142,8 @@ export class Vero implements INodeType { }; try { responseData = await veroApiRequest.call(this, 'POST', `/users/${operation}`, body); - } catch (err) { - throw new Error(`Vero Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } //https://developers.getvero.com/?bash#tags-add @@ -163,8 +165,8 @@ export class Vero implements INodeType { } try { responseData = await veroApiRequest.call(this, 'PUT', '/users/tags/edit', body); - } catch (err) { - throw new Error(`Vero Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } } @@ -216,8 +218,8 @@ export class Vero implements INodeType { } try { responseData = await veroApiRequest.call(this, 'POST', '/events/track', body); - } catch (err) { - throw new Error(`Vero Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } } diff --git a/packages/nodes-base/nodes/Vonage/GenericFunctions.ts b/packages/nodes-base/nodes/Vonage/GenericFunctions.ts index 0427d36890..5cbdc40d39 100644 --- a/packages/nodes-base/nodes/Vonage/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Vonage/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function vonageApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise { // tslint:disable-line:no-any @@ -35,16 +35,6 @@ export async function vonageApiRequest(this: IExecuteFunctions | IExecuteSingleF //@ts-ignore return await this.helpers.request.call(this, options); } catch (error) { - if (error.response && error.response.body && error.response.body.error) { - - let errors = error.response.body.error.errors; - - errors = errors.map((e: IDataObject) => e.message); - // Try to return the error prettier - throw new Error( - `Vonage error response [${error.statusCode}]: ${errors.join('|')}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Webflow/GenericFunctions.ts b/packages/nodes-base/nodes/Webflow/GenericFunctions.ts index 4632ec93a5..4eea1dd057 100644 --- a/packages/nodes-base/nodes/Webflow/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Webflow/GenericFunctions.ts @@ -11,7 +11,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function webflowApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -36,7 +36,7 @@ export async function webflowApiRequest(this: IHookFunctions | IExecuteFunctions if (authenticationMethod === 'accessToken') { const credentials = this.getCredentials('webflowApi'); 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}`; @@ -46,9 +46,6 @@ export async function webflowApiRequest(this: IHookFunctions | IExecuteFunctions return await this.helpers.requestOAuth2!.call(this, 'webflowOAuth2Api', options); } } catch (error) { - if (error.response.body.err) { - throw new Error(`Webflow Error: [${error.statusCode}]: ${error.response.body.err}`); - } - return error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts b/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts index becb8a9622..f9fd7d8394 100644 --- a/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts +++ b/packages/nodes-base/nodes/Webflow/WebflowTrigger.node.ts @@ -155,7 +155,7 @@ export class WebflowTrigger implements INodeType { const endpoint = `/sites/${siteId}/webhooks/${webhookData.webhookId}`; try { await webflowApiRequest.call(this, 'GET', endpoint); - } catch (err) { + } catch (error) { return false; } return true; diff --git a/packages/nodes-base/nodes/Webhook.node.ts b/packages/nodes-base/nodes/Webhook.node.ts index 351610fe70..ce8c80dcff 100644 --- a/packages/nodes-base/nodes/Webhook.node.ts +++ b/packages/nodes-base/nodes/Webhook.node.ts @@ -9,6 +9,8 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import * as basicAuth from 'basic-auth'; @@ -477,8 +479,8 @@ export class Webhook implements INodeType { }); }); - req.on('error', (err) => { - throw new Error(err.message); + req.on('error', (error) => { + throw new NodeOperationError(this.getNode(), error); }); }); } diff --git a/packages/nodes-base/nodes/Wekan/GenericFunctions.ts b/packages/nodes-base/nodes/Wekan/GenericFunctions.ts index e771b1e59d..4a652f2f6d 100644 --- a/packages/nodes-base/nodes/Wekan/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Wekan/GenericFunctions.ts @@ -13,6 +13,8 @@ import { import { ICredentialDataDecryptedObject, IDataObject, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; export async function getAuthorization( @@ -20,7 +22,7 @@ export async function getAuthorization( credentials?: ICredentialDataDecryptedObject, ): Promise { if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const { password, username } = credentials; @@ -39,7 +41,7 @@ export async function getAuthorization( return { token: response.token, userId: response.id }; } catch (error) { - throw new Error('Wekan Error: ' + error.error.reason); + throw new NodeApiError(this.getNode(), error); } } @@ -47,7 +49,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa const credentials = this.getCredentials('wekanApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } query = query || {}; @@ -70,7 +72,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa return await this.helpers.request!(options); } catch (error) { if (error.statusCode === 401) { - throw new Error('The Wekan credentials are not valid!'); + throw new NodeOperationError(this.getNode(), 'The Wekan credentials are not valid!'); } throw error; diff --git a/packages/nodes-base/nodes/Wekan/Wekan.node.ts b/packages/nodes-base/nodes/Wekan/Wekan.node.ts index 168ed65936..87f99d9df2 100644 --- a/packages/nodes-base/nodes/Wekan/Wekan.node.ts +++ b/packages/nodes-base/nodes/Wekan/Wekan.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -305,7 +306,7 @@ export class Wekan implements INodeType { endpoint = `users/${userId}/boards`; } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else if (resource === 'card') { @@ -395,7 +396,7 @@ export class Wekan implements INodeType { Object.assign(body, updateFields); } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else if (resource === 'cardComment') { @@ -454,7 +455,7 @@ export class Wekan implements INodeType { endpoint = `boards/${boardId}/cards/${cardId}/comments`; } 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') { @@ -509,7 +510,7 @@ export class Wekan implements INodeType { endpoint = `boards/${boardId}/lists`; } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else if (resource === 'checklist') { @@ -618,7 +619,7 @@ export class Wekan implements INodeType { } else { - throw new Error(`The operation "${operation}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`); } } else if (resource === 'checklistItem') { diff --git a/packages/nodes-base/nodes/Wise/GenericFunctions.ts b/packages/nodes-base/nodes/Wise/GenericFunctions.ts index a4aa44f430..15e751c633 100644 --- a/packages/nodes-base/nodes/Wise/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Wise/GenericFunctions.ts @@ -7,6 +7,7 @@ import { IDataObject, ILoadOptionsFunctions, INodeExecutionData, + NodeApiError, } from 'n8n-workflow'; import { @@ -60,15 +61,7 @@ export async function wiseApiRequest( try { return await this.helpers.request!(options); } catch (error) { - - const errors = error.error.errors; - - if (errors && Array.isArray(errors)) { - const errorMessage = errors.map((e) => e.message).join(' | '); - throw new Error(`Wise error response [${error.statusCode}]: ${errorMessage}`); - } - - throw new Error(`Wise error response [${error.statusCode}]: ${error}`); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/WooCommerce/GenericFunctions.ts b/packages/nodes-base/nodes/WooCommerce/GenericFunctions.ts index 227f369c92..f08f6e272e 100644 --- a/packages/nodes-base/nodes/WooCommerce/GenericFunctions.ts +++ b/packages/nodes-base/nodes/WooCommerce/GenericFunctions.ts @@ -1,4 +1,4 @@ -import { +import { OptionsWithUri, } from 'request'; @@ -12,6 +12,8 @@ import { import { ICredentialDataDecryptedObject, IDataObject, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -21,18 +23,18 @@ import { IShoppingLine, } from './OrderInterface'; -import { +import { createHash, } from 'crypto'; -import { +import { snakeCase, } from 'change-case'; export async function woocommerceApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('wooCommerceApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { auth: { @@ -49,22 +51,11 @@ export async function woocommerceApiRequest(this: IHookFunctions | IExecuteFunct delete options.form; } options = Object.assign({}, options, option); - + try { return await this.helpers.request!(options); } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The WooCommerce credentials are not valid!'); - } - - if (error.response.body && error.response.body.message) { - // Try to return the error prettier - throw new Error(`WooCommerce Error [${error.statusCode}]: ${error.response.body.message}`); - } - - // If that data does not exist for some reason return the actual error - throw new Error('WooCommerce Error: ' + error.message); + throw new NodeApiError(this.getNode(), error); } } @@ -145,4 +136,4 @@ export function toSnakeCase(data: } } } -} \ No newline at end of file +} diff --git a/packages/nodes-base/nodes/Wordpress/GenericFunctions.ts b/packages/nodes-base/nodes/Wordpress/GenericFunctions.ts index 61302655c8..5dfa4edfed 100644 --- a/packages/nodes-base/nodes/Wordpress/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Wordpress/GenericFunctions.ts @@ -9,13 +9,13 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function wordpressApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('wordpressApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { @@ -40,12 +40,7 @@ export async function wordpressApiRequest(this: IExecuteFunctions | IExecuteSing try { return await this.helpers.request!(options); } catch (error) { - let errorMessage = error.message; - if (error.response && error.response.body) { - errorMessage = error.response.body.message || error.response.body.Message || error.message; - } - - throw new Error('Wordpress Error: ' + errorMessage); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/WriteBinaryFile.node.ts b/packages/nodes-base/nodes/WriteBinaryFile.node.ts index 9794283529..6c6f9a83ad 100644 --- a/packages/nodes-base/nodes/WriteBinaryFile.node.ts +++ b/packages/nodes-base/nodes/WriteBinaryFile.node.ts @@ -8,6 +8,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { @@ -71,11 +72,11 @@ export class WriteBinaryFile implements INodeType { item = items[itemIndex]; if (item.binary === undefined) { - throw new Error('No binary data set. So file can not be written!'); + throw new NodeOperationError(this.getNode(), 'No binary data set. So file can not be written!'); } if (item.binary[dataPropertyName] === undefined) { - throw new Error(`The binary property "${dataPropertyName}" does not exist. So no file can be written!`); + throw new NodeOperationError(this.getNode(), `The binary property "${dataPropertyName}" does not exist. So no file can be written!`); } // Write the file to disk diff --git a/packages/nodes-base/nodes/Wufoo/GenericFunctions.ts b/packages/nodes-base/nodes/Wufoo/GenericFunctions.ts index 712a0aa639..e84164f311 100644 --- a/packages/nodes-base/nodes/Wufoo/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Wufoo/GenericFunctions.ts @@ -10,13 +10,13 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function wufooApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('wufooApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } let options: OptionsWithUri = { @@ -40,6 +40,6 @@ export async function wufooApiRequest(this: IHookFunctions | IExecuteFunctions | try { return await this.helpers.request!(options); } catch (error) { - throw new Error(error.message); + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Xero/GenericFunctions.ts b/packages/nodes-base/nodes/Xero/GenericFunctions.ts index 2678aa8f04..1ee4b040b9 100644 --- a/packages/nodes-base/nodes/Xero/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Xero/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, } from 'n8n-workflow'; export async function xeroApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -37,23 +37,7 @@ export async function xeroApiRequest(this: IExecuteFunctions | IExecuteSingleFun //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'xeroOAuth2Api', options); } catch (error) { - let errorMessage; - - if (error.response && error.response.body && error.response.body.Message) { - - errorMessage = error.response.body.Message; - - if (error.response.body.Elements) { - const elementErrors = []; - for (const element of error.response.body.Elements) { - elementErrors.push(element.ValidationErrors.map((error: IDataObject) => error.Message).join('|')); - } - errorMessage = elementErrors.join('-'); - } - // Try to return the error prettier - throw new Error(`Xero error response [${error.statusCode}]: ${errorMessage}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Xml.node.ts b/packages/nodes-base/nodes/Xml.node.ts index 914ed9da35..58717c2cc9 100644 --- a/packages/nodes-base/nodes/Xml.node.ts +++ b/packages/nodes-base/nodes/Xml.node.ts @@ -5,6 +5,7 @@ import { INodeExecutionData, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; @@ -245,7 +246,7 @@ export class Xml implements INodeType { const parser = new Parser(parserOptions); if (item.json[dataPropertyName] === undefined) { - throw new Error(`No json property "${dataPropertyName}" does not exists on item!`); + throw new NodeOperationError(this.getNode(), `No json property "${dataPropertyName}" does not exists on item!`); } // @ts-ignore @@ -260,7 +261,7 @@ export class Xml implements INodeType { }, }; } else { - throw new Error(`The operation "${mode}" is not known!`); + throw new NodeOperationError(this.getNode(), `The operation "${mode}" is not known!`); } } diff --git a/packages/nodes-base/nodes/Yourls/GenericFunctions.ts b/packages/nodes-base/nodes/Yourls/GenericFunctions.ts index 3ba2f5e3c5..9d49a8b4ab 100644 --- a/packages/nodes-base/nodes/Yourls/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Yourls/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function yourlsApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, body: any = {}, qs: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -31,22 +31,13 @@ export async function yourlsApiRequest(this: IExecuteFunctions | IExecuteSingleF const response = await this.helpers.request.call(this, options); if (response.status === 'fail') { - throw new Error( + throw new NodeOperationError(this.getNode(), `Yourls error response [400]: ${response.message}`, ); } return response; } catch (error) { - if (error.response && error.response.body && error.response.body.msg) { - - const message = error.response.body.msg; - - // Try to return the error prettier - throw new Error( - `Yourls error response [${error.statusCode}]: ${message}`, - ); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts b/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts index 3a1447997f..a7048b6ad6 100644 --- a/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zendesk/GenericFunctions.ts @@ -10,7 +10,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function zendeskApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -39,7 +39,7 @@ export async function zendeskApiRequest(this: IHookFunctions | IExecuteFunctions const credentials = this.getCredentials('zendeskApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const base64Key = Buffer.from(`${credentials.email}/token:${credentials.apiToken}`).toString('base64'); @@ -51,24 +51,15 @@ export async function zendeskApiRequest(this: IHookFunctions | IExecuteFunctions const credentials = this.getCredentials('zendeskOAuth2Api'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } options.uri = `https://${credentials.subdomain}.zendesk.com/api/v2${resource}.json`; return await this.helpers.requestOAuth2!.call(this, 'zendeskOAuth2Api', options); } - } catch(err) { - - let errorMessage = err.message; - if (err.response && err.response.body && err.response.body.error) { - errorMessage = err.response.body.error; - if (typeof err.response.body.error !== 'string') { - errorMessage = JSON.stringify(errorMessage); - } - } - - throw new Error(`Zendesk error response [${err.statusCode}]: ${errorMessage}`); + } catch(error) { + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts index b74812daa5..f77b8cc9d8 100644 --- a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts +++ b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts @@ -9,6 +9,8 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; import { @@ -255,7 +257,7 @@ export class Zendesk implements INodeType { Object.assign(body, JSON.parse(additionalFieldsJson)); } else { - throw new Error('Additional fields must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON'); } } @@ -307,7 +309,7 @@ export class Zendesk implements INodeType { Object.assign(body, JSON.parse(updateFieldsJson)); } else { - throw new Error('Additional fields must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON'); } } @@ -377,8 +379,8 @@ export class Zendesk implements INodeType { const ticketId = this.getNodeParameter('id', i) as string; try { responseData = await zendeskApiRequest.call(this, 'DELETE', `/tickets/${ticketId}`, {}); - } catch (err) { - throw new Error(`Zendesk Error: ${err}`); + } catch (error) { + throw new NodeApiError(this.getNode(), error); } } } diff --git a/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts b/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts index 44cc34d9a9..393a2f98d7 100644 --- a/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts +++ b/packages/nodes-base/nodes/Zendesk/ZendeskTrigger.node.ts @@ -14,6 +14,7 @@ import { INodeType, INodeTypeDescription, IWebhookResponseData, + NodeOperationError, } from 'n8n-workflow'; import { @@ -515,7 +516,7 @@ export class ZendeskTrigger implements INodeType { const conditions = this.getNodeParameter('conditions') as IDataObject; const options = this.getNodeParameter('options') as IDataObject; if (Object.keys(conditions).length === 0) { - throw new Error('You must have at least one condition'); + throw new NodeOperationError(this.getNode(), 'You must have at least one condition'); } if (options.fields) { // @ts-ignore diff --git a/packages/nodes-base/nodes/Zoho/GenericFunctions.ts b/packages/nodes-base/nodes/Zoho/GenericFunctions.ts index f70b13bb90..d21c84a9a3 100644 --- a/packages/nodes-base/nodes/Zoho/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zoho/GenericFunctions.ts @@ -5,7 +5,7 @@ import { ILoadOptionsFunctions, } from 'n8n-core'; import { - IDataObject + IDataObject, NodeApiError } from 'n8n-workflow'; export async function zohoApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -27,11 +27,7 @@ export async function zohoApiRequest(this: IExecuteFunctions | IExecuteSingleFun //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'zohoOAuth2Api', options); } catch (error) { - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - throw new Error(`Zoho error response [${error.statusCode}]: ${error.response.body.message}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Zoom/GenericFunctions.ts b/packages/nodes-base/nodes/Zoom/GenericFunctions.ts index 3d28b5790f..c85384274d 100644 --- a/packages/nodes-base/nodes/Zoom/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zoom/GenericFunctions.ts @@ -9,7 +9,7 @@ import { } from 'n8n-core'; import { - IDataObject, + IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; export async function zoomApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: object = {}, query: object = {}, headers: {} | undefined = undefined, option: {} = {}): Promise { // tslint:disable-line:no-any @@ -38,7 +38,7 @@ export async function zoomApiRequest(this: IExecuteFunctions | IExecuteSingleFun if (authenticationMethod === 'accessToken') { const credentials = this.getCredentials('zoomApi'); 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}`; @@ -49,18 +49,7 @@ export async function zoomApiRequest(this: IExecuteFunctions | IExecuteSingleFun return await this.helpers.requestOAuth2.call(this, 'zoomOAuth2Api', options); } } catch (error) { - if (error.statusCode === 401) { - // Return a clear error - throw new Error('The Zoom credentials are not valid!'); - } - - if (error.response && error.response.body && error.response.body.message) { - // Try to return the error prettier - throw new Error(`Zoom error response [${error.statusCode}]: ${error.response.body.message}`); - } - - // If that data does not exist for some reason return the actual error - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Zulip/GenericFunctions.ts b/packages/nodes-base/nodes/Zulip/GenericFunctions.ts index cf588440ea..a922f77cd6 100644 --- a/packages/nodes-base/nodes/Zulip/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zulip/GenericFunctions.ts @@ -8,7 +8,9 @@ import { import { IDataObject, IHookFunctions, - IWebhookFunctions + IWebhookFunctions, + NodeApiError, + NodeOperationError, } from 'n8n-workflow'; export async function zulipApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any @@ -16,7 +18,7 @@ export async function zulipApiRequest(this: IExecuteFunctions | IWebhookFunction const credentials = this.getCredentials('zulipApi'); if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); } const endpoint = `${credentials.url}/api/v1`; @@ -45,11 +47,7 @@ export async function zulipApiRequest(this: IExecuteFunctions | IWebhookFunction try { return await this.helpers.request!(options); } catch (error) { - if (error.response) { - const errorMessage = error.response.body.message || error.response.body.description || error.message; - throw new Error(`Zulip error response [${error.statusCode}]: ${errorMessage}`); - } - throw error; + throw new NodeApiError(this.getNode(), error); } } diff --git a/packages/nodes-base/nodes/Zulip/Zulip.node.ts b/packages/nodes-base/nodes/Zulip/Zulip.node.ts index 4c6a5d4002..15eadc4861 100644 --- a/packages/nodes-base/nodes/Zulip/Zulip.node.ts +++ b/packages/nodes-base/nodes/Zulip/Zulip.node.ts @@ -9,6 +9,7 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + NodeOperationError, } from 'n8n-workflow'; import { zulipApiRequest, @@ -210,11 +211,11 @@ export class Zulip implements INodeType { const credentials = this.getCredentials('zulipApi'); const binaryProperty = this.getNodeParameter('dataBinaryProperty', i) as string; 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 if (items[i].binary[binaryProperty] === undefined) { - throw new Error(`No binary data property "${binaryProperty}" does not exists on item!`); + throw new NodeOperationError(this.getNode(), `No binary data property "${binaryProperty}" does not exists on item!`); } const formData = { file: { @@ -283,7 +284,7 @@ export class Zulip implements INodeType { if (validateJSON(additionalFieldsJson) !== undefined) { Object.assign(body, JSON.parse(additionalFieldsJson)); } else { - throw new Error('Additional fields must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON'); } } @@ -342,7 +343,7 @@ export class Zulip implements INodeType { Object.assign(body, JSON.parse(additionalFieldsJson)); } else { - throw new Error('Additional fields must be a valid JSON'); + throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON'); } } diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index b57837379e..800dbb35a4 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -1,5 +1,7 @@ import { Workflow } from './Workflow'; import { WorkflowHooks } from './WorkflowHooks'; +import { WorkflowOperationError } from './WorkflowErrors'; +import { NodeApiError, NodeOperationError } from './NodeErrors'; import * as express from 'express'; export type IAllExecuteFunctions = IExecuteFunctions | IExecuteSingleFunctions | IHookFunctions | ILoadOptionsFunctions | IPollFunctions | ITriggerFunctions | IWebhookFunctions; @@ -32,11 +34,7 @@ export interface IConnection { index: number; } -export interface IExecutionError { - message: string; - node?: string; - stack?: string; -} +export type ExecutionError = WorkflowOperationError | NodeOperationError | NodeApiError; // Get used to gives nodes access to credentials export interface IGetCredentials { @@ -660,7 +658,7 @@ export interface IRunExecutionData { runNodeFilter?: string[]; }; resultData: { - error?: IExecutionError; + error?: ExecutionError; runData: IRunData; lastNodeExecuted?: string; }; @@ -683,7 +681,7 @@ export interface ITaskData { startTime: number; executionTime: number; data?: ITaskDataConnections; - error?: IExecutionError; + error?: ExecutionError; } @@ -761,7 +759,14 @@ export interface IWorkflowHooksOptionalParameters { sessionId?: string; } - export interface IWorkflowSettings { [key: string]: IDataObject | string | number | boolean | undefined; } + +export interface IRawErrorObject { + [key: string]: string | object | number | boolean | undefined | null | string[] | object[] | number[] | boolean[]; +} + +export interface IStatusCodeMessages { + [key: string]: string; +} diff --git a/packages/workflow/src/NodeErrors.ts b/packages/workflow/src/NodeErrors.ts new file mode 100644 index 0000000000..ef4ac30b0f --- /dev/null +++ b/packages/workflow/src/NodeErrors.ts @@ -0,0 +1,241 @@ +import { INode, IRawErrorObject, IStatusCodeMessages} from '.'; +import { parseString } from 'xml2js'; + +/** + * Top-level properties where an error message can be found in an API response. + */ +const ERROR_MESSAGE_PROPERTIES = [ + 'error', + 'message', + 'Message', + 'msg', + 'messages', + 'description', + 'reason', + 'detail', + 'details', + 'errors', + 'errorMessage', + 'errorMessages', + 'ErrorMessage', + 'error_message', + '_error_message', + 'errorDescription', + 'error_description', + 'error_summary', + 'title', + 'text', + 'field', + 'err', + 'type', +]; + +/** + * Top-level properties where an HTTP error code can be found in an API response. + */ +const ERROR_STATUS_PROPERTIES = ['statusCode', 'status', 'code', 'status_code', 'errorCode', 'error_code']; + +/** + * Properties where a nested object can be found in an API response. + */ +const ERROR_NESTING_PROPERTIES = ['error', 'err', 'response', 'body', 'data']; + +/** + * Base class for specific NodeError-types, with functionality for finding + * a value recursively inside an error object. + */ +abstract class NodeError extends Error { + description: string | null | undefined; + cause: Error | IRawErrorObject; + node: INode; + timestamp: number; + + constructor(node: INode, error: Error | IRawErrorObject) { + super(); + this.name = this.constructor.name; + this.cause = error; + this.node = node; + this.timestamp = Date.now(); + + if (error.message) { + this.message = error.message as string; + } + } + + /** + * Finds property through exploration based on potential keys and traversal keys. + * Depth-first approach. + * + * This method iterates over `potentialKeys` and, if the value at the key is a + * truthy value, the type of the value is checked: + * (1) if a string or number, the value is returned as a string; or + * (2) if an array, + * its string or number elements are collected as a long string, + * its object elements are traversed recursively (restart this function + * with each object as a starting point) + * + * If nothing found via `potentialKeys` this method iterates over `traversalKeys` and + * if the value at the key is a traversable object, it restarts with the object as the + * new starting point (recursion). + * If nothing found for any of the `traversalKeys`, exploration continues with remaining + * `traversalKeys`. + * + * Otherwise, if all the paths have been exhausted and no value is eligible, `null` is + * returned. + * + * @param {IRawErrorObject} error + * @param {string[]} potentialKeys + * @param {string[]} traversalKeys + * @returns {string | null} + */ + protected findProperty( + error: IRawErrorObject, + potentialKeys: string[], + traversalKeys: string[], + ): string | null { + for(const key of potentialKeys) { + if (error[key]) { + if (typeof error[key] === 'string') return error[key] as string; + if (typeof error[key] === 'number') return error[key]!.toString(); + if (Array.isArray(error[key])) { + // @ts-ignore + const resolvedErrors: string[] = error[key].map((error) => { + if (typeof error === 'string') return error; + if (typeof error === 'number') return error.toString(); + if (this.isTraversableObject(error)) { + return this.findProperty(error, potentialKeys, traversalKeys); + } + return null; + }) + .filter((errorValue: string | null) => errorValue !== null); + + if (resolvedErrors.length === 0) { + return null; + } + return resolvedErrors.join(' | '); + } + } + } + + for (const key of traversalKeys) { + if (this.isTraversableObject(error[key])) { + const property = this.findProperty(error[key] as IRawErrorObject, potentialKeys, traversalKeys); + if (property) { + return property; + } + } + } + + return null; + } + + /** + * Check if a value is an object with at least one key, i.e. it can be traversed. + */ + private isTraversableObject(value: any): value is IRawErrorObject { // tslint:disable-line:no-any + return value && typeof value === 'object' && !Array.isArray(value) && !!Object.keys(value).length; + } +} + +/** + * Class for instantiating an operational error, e.g. an invalid credentials error. + */ +export class NodeOperationError extends NodeError { + + constructor(node: INode, error: Error | string) { + if (typeof error === 'string') { + error = new Error(error); + } + super(node, error); + } +} + +const STATUS_CODE_MESSAGES: IStatusCodeMessages = { + '4XX': 'Your request is invalid or could not be processed by the service', + '400': 'Bad request - please check your parameters', + '401': 'Authorization failed - please check your credentials', + '402': 'Payment required - perhaps check your payment details?', + '403': 'Forbidden - perhaps check your credentials?', + '404': 'The resource you are requesting could not be found', + '405': 'Method not allowed - please check you are using the right HTTP method', + '429': 'The service is receiving too many requests from you! Perhaps take a break?', + + '5XX': 'The service failed to process your request', + '500': 'The service was not able to process your request', + '502': 'Bad gateway - the service failed to handle your request', + '503': 'Service unavailable - perhaps try again later?', + '504': 'Gateway timed out - perhaps try again later?', +}; + +const UNKNOWN_ERROR_MESSAGE = 'UNKNOWN ERROR - check the detailed error for more information'; + +/** + * Class for instantiating an error in an API response, e.g. a 404 Not Found response, + * with an HTTP error code, an error message and a description. + */ +export class NodeApiError extends NodeError { + httpCode: string | null; + + constructor( + node: INode, + error: IRawErrorObject, + { message, description, httpCode, parseXml }: { message?: string, description?: string, httpCode?: string, parseXml?: boolean } = {}, + ) { + super(node, error); + if (message) { + this.message = message; + this.description = description; + this.httpCode = httpCode ?? null; + return; + } + + this.httpCode = this.findProperty(error, ERROR_STATUS_PROPERTIES, ERROR_NESTING_PROPERTIES); + this.setMessage(); + + if (parseXml) { + this.setDescriptionFromXml(error.error as string); + return; + } + + this.description = this.findProperty(error, ERROR_MESSAGE_PROPERTIES, ERROR_NESTING_PROPERTIES); + } + + private setDescriptionFromXml(xml: string) { + parseString(xml, { explicitArray: false }, (_, result) => { + if (!result) return; + + const topLevelKey = Object.keys(result)[0]; + this.description = this.findProperty(result[topLevelKey], ERROR_MESSAGE_PROPERTIES, ['Error'].concat(ERROR_NESTING_PROPERTIES)); + }); + } + + /** + * Set the error's message based on the HTTP status code. + * + * @returns {void} + */ + private setMessage() { + + if (!this.httpCode) { + this.httpCode = null; + this.message = UNKNOWN_ERROR_MESSAGE; + return; + } + + if (STATUS_CODE_MESSAGES[this.httpCode]) { + this.message = STATUS_CODE_MESSAGES[this.httpCode]; + return; + } + + switch (this.httpCode.charAt(0)) { + case '4': + this.message = STATUS_CODE_MESSAGES['4XX']; + break; + case '5': + this.message = STATUS_CODE_MESSAGES['5XX']; + break; + default: + this.message = UNKNOWN_ERROR_MESSAGE; + } + } +} diff --git a/packages/workflow/src/Workflow.ts b/packages/workflow/src/Workflow.ts index 026fa80086..fb4189d762 100644 --- a/packages/workflow/src/Workflow.ts +++ b/packages/workflow/src/Workflow.ts @@ -937,6 +937,7 @@ export class Workflow { // The node did already fail. So throw an error here that it displays and logs it correctly. // Does get used by webhook and trigger nodes in case they throw an error that it is possible // to log the error and display in Editor-UI. + const error = new Error(runExecutionData.resultData.error.message); error.stack = runExecutionData.resultData.error.stack; throw error; diff --git a/packages/workflow/src/WorkflowErrors.ts b/packages/workflow/src/WorkflowErrors.ts new file mode 100644 index 0000000000..011a51224f --- /dev/null +++ b/packages/workflow/src/WorkflowErrors.ts @@ -0,0 +1,16 @@ +import { INode } from '.'; + +/** + * Class for instantiating an operational error, e.g. a timeout error. + */ +export class WorkflowOperationError extends Error { + node: INode | undefined; + timestamp: number; + + constructor(message: string, node?: INode, ) { + super(message); + this.name = this.constructor.name; + this.node = node; + this.timestamp = Date.now(); + } +} diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts index b2c24d55fb..1248d7da9d 100644 --- a/packages/workflow/src/index.ts +++ b/packages/workflow/src/index.ts @@ -1,7 +1,9 @@ export * from './Interfaces'; export * from './Expression'; +export * from './NodeErrors'; export * from './Workflow'; export * from './WorkflowDataProxy'; +export * from './WorkflowErrors'; export * from './WorkflowHooks'; import * as NodeHelpers from './NodeHelpers';