From d45324e7fe454976f734c92d8ea0ca17d5d8e824 Mon Sep 17 00:00:00 2001 From: Stanislas Polu Date: Fri, 26 Jan 2024 14:41:18 +0100 Subject: [PATCH] connectors API: improve error handling (#3443) * connectors API: improve error handling * iterate on types * re-align types * WithConnectorsAPIErrorResponse * move auth.ts to apiError * lint * nit * fix typo * nit typeguard --- connectors/src/api/connector_config.ts | 10 +- connectors/src/api/create_connector.ts | 4 +- connectors/src/api/delete_connector.ts | 4 +- connectors/src/api/get_connector.ts | 8 +- .../src/api/get_connector_permissions.ts | 8 +- connectors/src/api/get_resources_parents.ts | 5 +- connectors/src/api/get_resources_titles.ts | 5 +- connectors/src/api/resume_connector.ts | 8 +- .../src/api/set_connector_permissions.ts | 8 +- .../api/slack_channels_linked_with_agent.ts | 30 +++--- connectors/src/api/stop_connector.ts | 8 +- connectors/src/api/sync_connector.ts | 7 +- connectors/src/api/update_connector.ts | 20 ++-- connectors/src/api/webhooks/webhook_github.ts | 5 +- .../src/api/webhooks/webhook_google_drive.ts | 4 +- connectors/src/api/webhooks/webhook_slack.ts | 10 +- connectors/src/connectors/confluence/index.ts | 16 ++-- connectors/src/connectors/github/index.ts | 17 ++-- .../src/connectors/google_drive/index.ts | 23 ++--- connectors/src/connectors/intercom/index.ts | 28 +++--- connectors/src/connectors/interface.ts | 5 +- connectors/src/connectors/notion/index.ts | 23 ++--- connectors/src/connectors/slack/index.ts | 33 +++---- connectors/src/lib/error.ts | 23 ----- connectors/src/logger/withlogging.ts | 14 +-- connectors/src/middleware/auth.ts | 51 +++++++--- connectors/src/types/errors.ts | 7 -- front/admin/cli.ts | 4 +- front/lib/api/data_sources.ts | 4 +- .../[name]/managed/bot_enabled.ts | 6 +- .../[name]/managed/permissions/index.ts | 3 +- .../data_sources/[name]/managed/update.ts | 16 ++-- .../w/[wId]/builder/data-sources/managed.tsx | 2 +- types/src/connectors/api.ts | 42 ++++++++ types/src/front/lib/connectors_api.ts | 96 +++++++++---------- types/src/front/lib/error.ts | 4 +- types/src/index.ts | 1 + 37 files changed, 280 insertions(+), 282 deletions(-) create mode 100644 types/src/connectors/api.ts diff --git a/connectors/src/api/connector_config.ts b/connectors/src/api/connector_config.ts index 2d90743f9d9b..0a772a76279a 100644 --- a/connectors/src/api/connector_config.ts +++ b/connectors/src/api/connector_config.ts @@ -1,3 +1,4 @@ +import type { WithConnectorsAPIErrorReponse } from "@dust-tt/types"; import type { Request, Response } from "express"; import { isLeft } from "fp-ts/lib/Either"; import * as t from "io-ts"; @@ -9,16 +10,17 @@ import { } from "@connectors/connectors"; import { Connector } from "@connectors/lib/models"; import { apiError, withLogging } from "@connectors/logger/withlogging"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; const ConfigSetReqBodySchema = t.type({ configValue: t.string, }); type ConfigSetReqBody = t.TypeOf; -type ConfigGetResBody = - | { connectorId: number; configKey: string; configValue: string } - | ConnectorsAPIErrorResponse; +type ConfigGetResBody = WithConnectorsAPIErrorReponse<{ + connectorId: number; + configKey: string; + configValue: string; +}>; const _getConnectorConfig = async ( req: Request<{ connector_id: string; config_key: string }>, diff --git a/connectors/src/api/create_connector.ts b/connectors/src/api/create_connector.ts index 12baf9c0ced7..6692830b8d96 100644 --- a/connectors/src/api/create_connector.ts +++ b/connectors/src/api/create_connector.ts @@ -4,6 +4,7 @@ import type { CreateConnectorOAuthRequestBodySchema, CreateConnectorUrlRequestBodySchema, Result, + WithConnectorsAPIErrorReponse, } from "@dust-tt/types"; import { assertNever, @@ -24,9 +25,8 @@ import { errorFromAny } from "@connectors/lib/error"; import { Connector } from "@connectors/lib/models"; import logger from "@connectors/logger/logger"; import { apiError, withLogging } from "@connectors/logger/withlogging"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; -type ConnectorCreateResBody = ConnectorType | ConnectorsAPIErrorResponse; +type ConnectorCreateResBody = WithConnectorsAPIErrorReponse; const provider2createConnectorType: Record = { diff --git a/connectors/src/api/delete_connector.ts b/connectors/src/api/delete_connector.ts index 7e52a7fd7f87..930ccbbdb136 100644 --- a/connectors/src/api/delete_connector.ts +++ b/connectors/src/api/delete_connector.ts @@ -1,3 +1,4 @@ +import type { WithConnectorsAPIErrorReponse } from "@dust-tt/types"; import type { Request, Response } from "express"; import { @@ -7,14 +8,13 @@ import { import { Connector } from "@connectors/lib/models"; import { terminateAllWorkflowsForConnectorId } from "@connectors/lib/temporal"; import { apiError, withLogging } from "@connectors/logger/withlogging"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; type ConnectorDeleteReqBody = { dataSourceName: string; workspaceId: string; }; -type ConnectorDeleteResBody = { success: true } | ConnectorsAPIErrorResponse; +type ConnectorDeleteResBody = WithConnectorsAPIErrorReponse<{ success: true }>; const _deleteConnectorAPIHandler = async ( req: Request< diff --git a/connectors/src/api/get_connector.ts b/connectors/src/api/get_connector.ts index 40895391d934..be2799b868e4 100644 --- a/connectors/src/api/get_connector.ts +++ b/connectors/src/api/get_connector.ts @@ -1,13 +1,15 @@ -import type { ConnectorType } from "@dust-tt/types"; +import type { + ConnectorType, + WithConnectorsAPIErrorReponse, +} from "@dust-tt/types"; import type { Request, Response } from "express"; import { Connector } from "@connectors/lib/models"; import { GithubDiscussion, GithubIssue } from "@connectors/lib/models/github"; import { NotionPage } from "@connectors/lib/models/notion"; import { apiError, withLogging } from "@connectors/logger/withlogging"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; -type GetConnectorRes = ConnectorType | ConnectorsAPIErrorResponse; +type GetConnectorRes = WithConnectorsAPIErrorReponse; const _getConnector = async ( req: Request<{ connector_id: string }, GetConnectorRes, undefined>, diff --git a/connectors/src/api/get_connector_permissions.ts b/connectors/src/api/get_connector_permissions.ts index b11fbb895325..0ee3e3c43f64 100644 --- a/connectors/src/api/get_connector_permissions.ts +++ b/connectors/src/api/get_connector_permissions.ts @@ -1,17 +1,17 @@ +import type { WithConnectorsAPIErrorReponse } from "@dust-tt/types"; import type { Request, Response } from "express"; import { RETRIEVE_CONNECTOR_PERMISSIONS_BY_TYPE } from "@connectors/connectors"; import { Connector } from "@connectors/lib/models"; import { apiError, withLogging } from "@connectors/logger/withlogging"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; import type { ConnectorPermission, ConnectorResource, } from "@connectors/types/resources"; -type GetConnectorPermissionsRes = - | { resources: ConnectorResource[] } - | ConnectorsAPIErrorResponse; +type GetConnectorPermissionsRes = WithConnectorsAPIErrorReponse<{ + resources: ConnectorResource[]; +}>; const _getConnectorPermissions = async ( req: Request<{ connector_id: string }, GetConnectorPermissionsRes, undefined>, diff --git a/connectors/src/api/get_resources_parents.ts b/connectors/src/api/get_resources_parents.ts index 95ffe32fe0d9..04f046e64b28 100644 --- a/connectors/src/api/get_resources_parents.ts +++ b/connectors/src/api/get_resources_parents.ts @@ -1,3 +1,4 @@ +import type { WithConnectorsAPIErrorReponse } from "@dust-tt/types"; import type { Request, Response } from "express"; import { zip } from "fp-ts/lib/Array"; import { isLeft } from "fp-ts/lib/Either"; @@ -17,12 +18,12 @@ export type GetResourcesParentsRequestBody = t.TypeOf< typeof GetResourcesParentsRequestBodySchema >; -type GetResourcesParentsResponseBody = { +type GetResourcesParentsResponseBody = WithConnectorsAPIErrorReponse<{ resources: { internalId: string; parents: string[] | null; }[]; -}; +}>; const _getResourcesParents = async ( req: Request< diff --git a/connectors/src/api/get_resources_titles.ts b/connectors/src/api/get_resources_titles.ts index 47706d6ef19f..6939808997f4 100644 --- a/connectors/src/api/get_resources_titles.ts +++ b/connectors/src/api/get_resources_titles.ts @@ -1,3 +1,4 @@ +import type { WithConnectorsAPIErrorReponse } from "@dust-tt/types"; import type { Request, Response } from "express"; import { isLeft } from "fp-ts/lib/Either"; import * as t from "io-ts"; @@ -15,12 +16,12 @@ type GetResourcesTitlesRequestBody = t.TypeOf< typeof GetResourcesTitlesRequestBodySchema >; -type GetResourcesTitlesResponseBody = { +type GetResourcesTitlesResponseBody = WithConnectorsAPIErrorReponse<{ resources: { internalId: string; title: string | null; }[]; -}; +}>; const _getResourcesTitles = async ( req: Request< diff --git a/connectors/src/api/resume_connector.ts b/connectors/src/api/resume_connector.ts index 6d24064acf1d..5640a077b58b 100644 --- a/connectors/src/api/resume_connector.ts +++ b/connectors/src/api/resume_connector.ts @@ -1,3 +1,4 @@ +import type { WithConnectorsAPIErrorReponse } from "@dust-tt/types"; import type { Request, Response } from "express"; import { RESUME_CONNECTOR_BY_TYPE } from "@connectors/connectors"; @@ -5,11 +6,10 @@ import { errorFromAny } from "@connectors/lib/error"; import { Connector } from "@connectors/lib/models"; import logger from "@connectors/logger/logger"; import { apiError, withLogging } from "@connectors/logger/withlogging"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; -type ConnectorResumeResBody = - | { connectorId: string } - | ConnectorsAPIErrorResponse; +type ConnectorResumeResBody = WithConnectorsAPIErrorReponse<{ + connectorId: string; +}>; const _resumeConnectorAPIHandler = async ( req: Request<{ connector_id: string }, ConnectorResumeResBody>, diff --git a/connectors/src/api/set_connector_permissions.ts b/connectors/src/api/set_connector_permissions.ts index 877bb01ae577..7c60f1f74cb9 100644 --- a/connectors/src/api/set_connector_permissions.ts +++ b/connectors/src/api/set_connector_permissions.ts @@ -1,3 +1,4 @@ +import type { WithConnectorsAPIErrorReponse } from "@dust-tt/types"; import type { Request, Response } from "express"; import { isLeft } from "fp-ts/lib/Either"; import * as t from "io-ts"; @@ -6,11 +7,10 @@ import * as reporter from "io-ts-reporters"; import { SET_CONNECTOR_PERMISSIONS_BY_TYPE } from "@connectors/connectors"; import { Connector } from "@connectors/lib/models"; import { apiError, withLogging } from "@connectors/logger/withlogging"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; -type SetConnectorPermissionsRes = - | { success: true } - | ConnectorsAPIErrorResponse; +type SetConnectorPermissionsRes = WithConnectorsAPIErrorReponse<{ + success: true; +}>; const SetConnectorPermissionsRequestBodySchema = t.type({ resources: t.array( diff --git a/connectors/src/api/slack_channels_linked_with_agent.ts b/connectors/src/api/slack_channels_linked_with_agent.ts index 10023e39ef22..72a32ace75fc 100644 --- a/connectors/src/api/slack_channels_linked_with_agent.ts +++ b/connectors/src/api/slack_channels_linked_with_agent.ts @@ -1,3 +1,4 @@ +import type { WithConnectorsAPIErrorReponse } from "@dust-tt/types"; import type { Request, Response } from "express"; import { isLeft } from "fp-ts/lib/Either"; import * as t from "io-ts"; @@ -6,7 +7,6 @@ import { Op } from "sequelize"; import { joinChannel } from "@connectors/connectors/slack/lib/channels"; import { getChannels } from "@connectors/connectors/slack/temporal/activities"; -import type { APIErrorWithStatusCode } from "@connectors/lib/error"; import { sequelize_conn } from "@connectors/lib/models"; import { SlackChannel } from "@connectors/lib/models/slack"; import { apiError, withLogging } from "@connectors/logger/withlogging"; @@ -21,9 +21,9 @@ type PatchSlackChannelsLinkedWithAgentReqBody = t.TypeOf< typeof PatchSlackChannelsLinkedWithAgentReqBodySchema >; -type PatchSlackChannelsLinkedWithAgentResBody = - | { success: true } - | APIErrorWithStatusCode; +type PatchSlackChannelsLinkedWithAgentResBody = WithConnectorsAPIErrorReponse<{ + success: true; +}>; const _patchSlackChannelsLinkedWithAgentHandler = async ( req: Request< @@ -152,22 +152,16 @@ export const patchSlackChannelsLinkedWithAgentHandler = withLogging( _patchSlackChannelsLinkedWithAgentHandler ); -type GetSlackChannelsLinkedWithAgentResBody = - | { - slackChannels: { - slackChannelId: string; - slackChannelName: string; - agentConfigurationId: string; - }[]; - } - | APIErrorWithStatusCode; +type GetSlackChannelsLinkedWithAgentResBody = WithConnectorsAPIErrorReponse<{ + slackChannels: { + slackChannelId: string; + slackChannelName: string; + agentConfigurationId: string; + }[]; +}>; const _getSlackChannelsLinkedWithAgentHandler = async ( - req: Request< - Record, - { slackChannelIds: string[] } | APIErrorWithStatusCode, - undefined - >, + req: Request>, res: Response ) => { const { connector_id: connectorId } = req.query; diff --git a/connectors/src/api/stop_connector.ts b/connectors/src/api/stop_connector.ts index 7aa34acecb74..50f6f1a2fa3c 100644 --- a/connectors/src/api/stop_connector.ts +++ b/connectors/src/api/stop_connector.ts @@ -1,3 +1,4 @@ +import type { WithConnectorsAPIErrorReponse } from "@dust-tt/types"; import type { Request, Response } from "express"; import { STOP_CONNECTOR_BY_TYPE } from "@connectors/connectors"; @@ -5,11 +6,10 @@ import { errorFromAny } from "@connectors/lib/error"; import { Connector } from "@connectors/lib/models"; import logger from "@connectors/logger/logger"; import { apiError, withLogging } from "@connectors/logger/withlogging"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; -type ConnectorStopResBody = - | { connectorId: string } - | ConnectorsAPIErrorResponse; +type ConnectorStopResBody = WithConnectorsAPIErrorReponse<{ + connectorId: string; +}>; const _stopConnectorAPIHandler = async ( req: Request<{ connector_id: string }, ConnectorStopResBody>, diff --git a/connectors/src/api/sync_connector.ts b/connectors/src/api/sync_connector.ts index 61e33cdc43d6..da12b1cb6290 100644 --- a/connectors/src/api/sync_connector.ts +++ b/connectors/src/api/sync_connector.ts @@ -1,11 +1,11 @@ +import type { WithConnectorsAPIErrorReponse } from "@dust-tt/types"; import type { Request, Response } from "express"; import { SYNC_CONNECTOR_BY_TYPE } from "@connectors/connectors"; import { Connector } from "@connectors/lib/models"; import { withLogging } from "@connectors/logger/withlogging"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; -type GetSyncStatusRes = { workflowId: string } | ConnectorsAPIErrorResponse; +type GetSyncStatusRes = WithConnectorsAPIErrorReponse<{ workflowId: string }>; const _syncConnectorAPIHandler = async ( req: Request<{ connector_id: string }, GetSyncStatusRes, undefined>, @@ -14,6 +14,7 @@ const _syncConnectorAPIHandler = async ( if (!req.params.connector_id) { res.status(400).send({ error: { + type: "invalid_request_error", message: `Missing required parameters. Required : connector_id`, }, }); @@ -25,6 +26,7 @@ const _syncConnectorAPIHandler = async ( if (!connector) { res.status(404).send({ error: { + type: "connector_not_found", message: `Connector with id ${req.params.connector_id} not found`, }, }); @@ -37,6 +39,7 @@ const _syncConnectorAPIHandler = async ( if (launchRes.isErr()) { res.status(500).send({ error: { + type: "internal_server_error", message: launchRes.error.message, }, }); diff --git a/connectors/src/api/update_connector.ts b/connectors/src/api/update_connector.ts index 1765e3a326ca..23db18624763 100644 --- a/connectors/src/api/update_connector.ts +++ b/connectors/src/api/update_connector.ts @@ -1,16 +1,16 @@ +import type { WithConnectorsAPIErrorReponse } from "@dust-tt/types"; import type { Request, Response } from "express"; import { UPDATE_CONNECTOR_BY_TYPE } from "@connectors/connectors"; import { Connector } from "@connectors/lib/models"; import { apiError, withLogging } from "@connectors/logger/withlogging"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; type ConnectorUpdateReqBody = { connectionId?: string | null; }; -type ConnectorUpdateResBody = - | { connectorId: string } - | ConnectorsAPIErrorResponse; +type ConnectorUpdateResBody = WithConnectorsAPIErrorReponse<{ + connectorId: string; +}>; const _getConnectorUpdateAPIHandler = async ( req: Request<{ connector_id: string }, ConnectorUpdateReqBody>, @@ -44,15 +44,9 @@ const _getConnectorUpdateAPIHandler = async ( }); if (updateRes.isErr()) { - const errorRes = updateRes as { error: ConnectorsAPIErrorResponse }; - const error = errorRes.error.error; - - if (error.type === "connector_oauth_target_mismatch") { + if (updateRes.error.type === "connector_oauth_target_mismatch") { return apiError(req, res, { - api_error: { - type: error.type, - message: error.message, - }, + api_error: updateRes.error, status_code: 401, }); } else { @@ -60,7 +54,7 @@ const _getConnectorUpdateAPIHandler = async ( status_code: 500, api_error: { type: "internal_server_error", - message: `Could not update the connector: ${error.message}`, + message: `Could not update the connector: ${updateRes.error.message}`, }, }); } diff --git a/connectors/src/api/webhooks/webhook_github.ts b/connectors/src/api/webhooks/webhook_github.ts index 732dd3677909..506cbfbddcc6 100644 --- a/connectors/src/api/webhooks/webhook_github.ts +++ b/connectors/src/api/webhooks/webhook_github.ts @@ -1,3 +1,4 @@ +import type { WithConnectorsAPIErrorReponse } from "@dust-tt/types"; import { assertNever } from "@dust-tt/types"; import type { Request, Response } from "express"; import { isLeft } from "fp-ts/lib/Either"; @@ -29,7 +30,6 @@ import { } from "@connectors/lib/models/github"; import mainLogger from "@connectors/logger/logger"; import { withLogging } from "@connectors/logger/withlogging"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; const HANDLED_WEBHOOKS = { installation_repositories: new Set(["added", "removed"]), @@ -42,7 +42,7 @@ const HANDLED_WEBHOOKS = { const logger = mainLogger.child({ provider: "github" }); -type GithubWebhookResBody = null | ConnectorsAPIErrorResponse; +type GithubWebhookResBody = WithConnectorsAPIErrorReponse; const _webhookGithubAPIHandler = async ( req: Request< @@ -59,6 +59,7 @@ const _webhookGithubAPIHandler = async ( if (!event || typeof event !== "string") { return res.status(400).json({ error: { + type: "invalid_request_error", message: "Missing `x-github-event` header", }, }); diff --git a/connectors/src/api/webhooks/webhook_google_drive.ts b/connectors/src/api/webhooks/webhook_google_drive.ts index 191f4bc34485..f7c17051c801 100644 --- a/connectors/src/api/webhooks/webhook_google_drive.ts +++ b/connectors/src/api/webhooks/webhook_google_drive.ts @@ -1,13 +1,13 @@ +import type { WithConnectorsAPIErrorReponse } from "@dust-tt/types"; import { RateLimitError } from "@dust-tt/types"; import type { Request, Response } from "express"; import { launchGoogleDriveIncrementalSyncWorkflow } from "@connectors/connectors/google_drive/temporal/client"; -import type { APIErrorWithStatusCode } from "@connectors/lib/error"; import { GoogleDriveWebhook } from "@connectors/lib/models/google_drive"; import logger from "@connectors/logger/logger"; import { apiError, withLogging } from "@connectors/logger/withlogging"; -type GoogleDriveWebhookResBody = null | APIErrorWithStatusCode; +type GoogleDriveWebhookResBody = WithConnectorsAPIErrorReponse; const _webhookGoogleDriveAPIHandler = async ( req: Request, GoogleDriveWebhookResBody>, diff --git a/connectors/src/api/webhooks/webhook_slack.ts b/connectors/src/api/webhooks/webhook_slack.ts index 8fd827f3c352..27c12dd040b1 100644 --- a/connectors/src/api/webhooks/webhook_slack.ts +++ b/connectors/src/api/webhooks/webhook_slack.ts @@ -1,3 +1,4 @@ +import type { WithConnectorsAPIErrorReponse } from "@dust-tt/types"; import type { Request, Response } from "express"; import { botAnswerMessageWithErrorHandling } from "@connectors/connectors/slack/bot"; @@ -7,7 +8,6 @@ import { launchSlackSyncOneThreadWorkflow, } from "@connectors/connectors/slack/temporal/client"; import { launchSlackGarbageCollectWorkflow } from "@connectors/connectors/slack/temporal/client"; -import type { APIErrorWithStatusCode } from "@connectors/lib/error"; import { Connector } from "@connectors/lib/models"; import { SlackChannel, SlackConfiguration } from "@connectors/lib/models/slack"; import { Ok } from "@connectors/lib/result"; @@ -35,10 +35,9 @@ type SlackWebhookReqBody = { }; }; -type SlackWebhookResBody = - | { challenge: string } - | null - | APIErrorWithStatusCode; +type SlackWebhookResBody = WithConnectorsAPIErrorReponse<{ + challenge: string; +} | null>; async function handleChatBot(req: Request, res: Response, logger: Logger) { const { event } = req.body; @@ -399,7 +398,6 @@ const _webhookSlackAPIHandler = async ( ); return res.status(200).send(); } - break; } } diff --git a/connectors/src/connectors/confluence/index.ts b/connectors/src/connectors/confluence/index.ts index 9b87af101ddd..3ecd312502fa 100644 --- a/connectors/src/connectors/confluence/index.ts +++ b/connectors/src/connectors/confluence/index.ts @@ -1,9 +1,9 @@ import type { ConnectorPermission, ConnectorResource, + ConnectorsAPIError, ModelId, } from "@dust-tt/types"; -import type { ConnectorsAPIErrorResponse } from "@dust-tt/types"; import { confluenceConfig } from "@connectors/connectors/confluence/lib/config"; import { @@ -119,7 +119,7 @@ export async function updateConfluenceConnector( }: { connectionId?: NangoConnectionId | null; } -): Promise> { +): Promise> { const connector = await Connector.findOne({ where: { id: connectorId, @@ -128,10 +128,8 @@ export async function updateConfluenceConnector( if (!connector) { logger.error({ connectorId }, "Connector not found."); return new Err({ - error: { - message: "Connector not found", - type: "connector_not_found", - }, + message: "Connector not found", + type: "connector_not_found", }); } @@ -177,10 +175,8 @@ export async function updateConfluenceConnector( ); return new Err({ - error: { - type: "connector_oauth_target_mismatch", - message: "Cannot change workspace of a Notion connector", - }, + type: "connector_oauth_target_mismatch", + message: "Cannot change the workspace of a Notion connector", }); } } diff --git a/connectors/src/connectors/github/index.ts b/connectors/src/connectors/github/index.ts index db86c1fb9746..2e6cd8197240 100644 --- a/connectors/src/connectors/github/index.ts +++ b/connectors/src/connectors/github/index.ts @@ -1,4 +1,4 @@ -import type { ModelId } from "@dust-tt/types"; +import type { ConnectorsAPIError, ModelId } from "@dust-tt/types"; import { getRepo, @@ -19,7 +19,6 @@ import type { Result } from "@connectors/lib/result"; import { Err, Ok } from "@connectors/lib/result"; import mainLogger from "@connectors/logger/logger"; import type { DataSourceConfig } from "@connectors/types/data_source_config"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; import type { ConnectorPermission, ConnectorResource, @@ -85,7 +84,7 @@ export async function updateGithubConnector( }: { connectionId?: string | null; } -): Promise> { +): Promise> { const c = await Connector.findOne({ where: { id: connectorId, @@ -94,10 +93,8 @@ export async function updateGithubConnector( if (!c) { logger.error({ connectorId }, "Connector not found"); return new Err({ - error: { - message: "Connector not found", - type: "connector_not_found", - }, + message: "Connector not found", + type: "connector_not_found", }); } @@ -107,10 +104,8 @@ export async function updateGithubConnector( if (oldGithubInstallationId !== newGithubInstallationId) { return new Err({ - error: { - type: "connector_oauth_target_mismatch", - message: "Cannot change the Installation Id of a Github Data Source", - }, + type: "connector_oauth_target_mismatch", + message: "Cannot change the Installation Id of a Github Data Source", }); } diff --git a/connectors/src/connectors/google_drive/index.ts b/connectors/src/connectors/google_drive/index.ts index a33a5fc55d35..4bb57e4f98a3 100644 --- a/connectors/src/connectors/google_drive/index.ts +++ b/connectors/src/connectors/google_drive/index.ts @@ -20,7 +20,6 @@ import type { Result } from "@connectors/lib/result.js"; import { Err, Ok } from "@connectors/lib/result.js"; import logger from "@connectors/logger/logger"; import type { DataSourceConfig } from "@connectors/types/data_source_config.js"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; import type { ConnectorPermission, ConnectorResource, @@ -42,7 +41,7 @@ import { launchGoogleGarbageCollector, } from "./temporal/client"; export type NangoConnectionId = string; -import type { ModelId } from "@dust-tt/types"; +import type { ConnectorsAPIError, ModelId } from "@dust-tt/types"; import { v4 as uuidv4 } from "uuid"; const { @@ -161,7 +160,7 @@ export async function updateGoogleDriveConnector( }: { connectionId?: NangoConnectionId | null; } -): Promise> { +): Promise> { if (!NANGO_GOOGLE_DRIVE_CONNECTOR_ID) { throw new Error("NANGO_GOOGLE_DRIVE_CONNECTOR_ID not set"); } @@ -174,10 +173,8 @@ export async function updateGoogleDriveConnector( if (!c) { logger.error({ connectorId }, "Connector not found"); return new Err({ - error: { - message: "Connector not found", - type: "connector_not_found", - }, + message: "Connector not found", + type: "connector_not_found", }); } @@ -203,19 +200,15 @@ export async function updateGoogleDriveConnector( if (!currentDriveUserDomain || !newDriveUserDomain) { return new Err({ - error: { - type: "connector_update_error", - message: "Error retrieving google drive info to update connector", - }, + type: "connector_update_error", + message: "Error retrieving google drive info to update connector", }); } if (currentDriveUserDomain !== newDriveUserDomain) { return new Err({ - error: { - type: "connector_oauth_target_mismatch", - message: "Cannot change domain of a Google Drive connector", - }, + type: "connector_oauth_target_mismatch", + message: "Cannot change domain of a Google Drive connector", }); } } catch (e) { diff --git a/connectors/src/connectors/intercom/index.ts b/connectors/src/connectors/intercom/index.ts index 02812b7f4539..c7fb1d55d57f 100644 --- a/connectors/src/connectors/intercom/index.ts +++ b/connectors/src/connectors/intercom/index.ts @@ -1,4 +1,9 @@ -import type { ConnectorPermission, ModelId, Result } from "@dust-tt/types"; +import type { + ConnectorPermission, + ConnectorsAPIError, + ModelId, + Result, +} from "@dust-tt/types"; import { Op } from "sequelize"; import { @@ -28,7 +33,6 @@ import { nangoDeleteConnection } from "@connectors/lib/nango_client"; import { Err, Ok } from "@connectors/lib/result"; import logger from "@connectors/logger/logger"; import type { DataSourceConfig } from "@connectors/types/data_source_config"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; import type { NangoConnectionId } from "@connectors/types/nango_connection_id"; import type { ConnectorResource } from "@connectors/types/resources"; @@ -70,7 +74,7 @@ export async function updateIntercomConnector( }: { connectionId?: NangoConnectionId | null; } -): Promise> { +): Promise> { if (!NANGO_INTERCOM_CONNECTOR_ID) { throw new Error("NANGO_INTERCOM_CONNECTOR_ID not set"); } @@ -79,10 +83,8 @@ export async function updateIntercomConnector( if (!connector) { logger.error({ connectorId }, "[Intercom] Connector not found."); return new Err({ - error: { - message: "Connector not found", - type: "connector_not_found", - }, + message: "Connector not found", + type: "connector_not_found", }); } @@ -99,10 +101,8 @@ export async function updateIntercomConnector( if (!oldIntercomWorkspaceId || !newIntercomWorkspaceId) { return new Err({ - error: { - type: "connector_update_error", - message: "Error retrieving nango connection info to update connector", - }, + type: "connector_update_error", + message: "Error retrieving nango connection info to update connector", }); } if (oldIntercomWorkspaceId !== newIntercomWorkspaceId) { @@ -115,10 +115,8 @@ export async function updateIntercomConnector( } ); return new Err({ - error: { - type: "connector_oauth_target_mismatch", - message: "Cannot change workspace of a Notion connector", - }, + type: "connector_oauth_target_mismatch", + message: "Cannot change workspace of a Notion connector", }); } diff --git a/connectors/src/connectors/interface.ts b/connectors/src/connectors/interface.ts index badbee321373..cb243cd415f5 100644 --- a/connectors/src/connectors/interface.ts +++ b/connectors/src/connectors/interface.ts @@ -1,8 +1,7 @@ -import type { ModelId } from "@dust-tt/types"; +import type { ConnectorsAPIError, ModelId } from "@dust-tt/types"; import type { Result } from "@connectors/lib/result"; import type { DataSourceConfig } from "@connectors/types/data_source_config"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; import type { ConnectorPermission, ConnectorResource, @@ -23,7 +22,7 @@ export type ConnectorUpdater = ( params: { connectionId?: string | null; } -) => Promise>; +) => Promise>; export type ConnectorStopper = ( connectorId: string diff --git a/connectors/src/connectors/notion/index.ts b/connectors/src/connectors/notion/index.ts index 674d62ed7058..44b993414355 100644 --- a/connectors/src/connectors/notion/index.ts +++ b/connectors/src/connectors/notion/index.ts @@ -1,4 +1,4 @@ -import type { ModelId } from "@dust-tt/types"; +import type { ConnectorsAPIError, ModelId } from "@dust-tt/types"; import { v4 as uuidv4 } from "uuid"; import { notionConfig } from "@connectors/connectors/notion/lib/config"; @@ -23,7 +23,6 @@ import type { Result } from "@connectors/lib/result"; import { Err, Ok } from "@connectors/lib/result"; import mainLogger from "@connectors/logger/logger"; import type { DataSourceConfig } from "@connectors/types/data_source_config"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; import type { NangoConnectionId } from "@connectors/types/nango_connection_id"; import type { ConnectorResource } from "@connectors/types/resources"; @@ -87,7 +86,7 @@ export async function updateNotionConnector( }: { connectionId?: NangoConnectionId | null; } -): Promise> { +): Promise> { const c = await Connector.findOne({ where: { id: connectorId, @@ -96,10 +95,8 @@ export async function updateNotionConnector( if (!c) { logger.error({ connectorId }, "Connector not found"); return new Err({ - error: { - message: "Connector not found", - type: "connector_not_found", - }, + message: "Connector not found", + type: "connector_not_found", }); } @@ -122,18 +119,14 @@ export async function updateNotionConnector( if (!workspaceId || !newWorkspaceId) { return new Err({ - error: { - type: "connector_update_error", - message: "Error retrieving nango connection info to update connector", - }, + type: "connector_update_error", + message: "Error retrieving nango connection info to update connector", }); } if (workspaceId !== newWorkspaceId) { return new Err({ - error: { - type: "connector_oauth_target_mismatch", - message: "Cannot change workspace of a Notion connector", - }, + type: "connector_oauth_target_mismatch", + message: "Cannot change workspace of a Notion connector", }); } diff --git a/connectors/src/connectors/slack/index.ts b/connectors/src/connectors/slack/index.ts index 5c970518ea0e..c4c5ff9e5d2a 100644 --- a/connectors/src/connectors/slack/index.ts +++ b/connectors/src/connectors/slack/index.ts @@ -1,4 +1,4 @@ -import type { ModelId } from "@dust-tt/types"; +import type { ConnectorsAPIError, ModelId } from "@dust-tt/types"; import { WebClient } from "@slack/web-api"; import PQueue from "p-queue"; @@ -32,7 +32,6 @@ import type { Result } from "@connectors/lib/result.js"; import { Err, Ok } from "@connectors/lib/result.js"; import logger from "@connectors/logger/logger"; import type { DataSourceConfig } from "@connectors/types/data_source_config.js"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; import type { NangoConnectionId } from "@connectors/types/nango_connection_id"; import type { ConnectorPermission, @@ -127,7 +126,7 @@ export async function updateSlackConnector( }: { connectionId?: string | null; } -): Promise> { +): Promise> { if (!NANGO_SLACK_CONNECTOR_ID) { throw new Error("NANGO_SLACK_CONNECTOR_ID not set"); } @@ -140,10 +139,8 @@ export async function updateSlackConnector( if (!c) { logger.error({ connectorId }, "Connector not found"); return new Err({ - error: { - message: "Connector not found", - type: "connector_not_found", - }, + message: "Connector not found", + type: "connector_not_found", }); } @@ -155,10 +152,8 @@ export async function updateSlackConnector( if (!currentSlackConfig) { logger.error({ connectorId }, "Slack configuration not found"); return new Err({ - error: { - message: "Slack configuration not found", - type: "connector_not_found", - }, + message: "Slack configuration not found", + type: "connector_not_found", }); } @@ -170,9 +165,8 @@ export async function updateSlackConnector( const teamInfoRes = await slackClient.team.info(); if (!teamInfoRes.ok || !teamInfoRes.team?.id) { return new Err({ - error: { - message: "Can't get the Slack team information.", - }, + type: "internal_server_error", + message: "Can't get the Slack team information.", }); } @@ -198,9 +192,8 @@ export async function updateSlackConnector( if (uninstallRes.isErr()) { return new Err({ - error: { - message: "Failed to deactivate the mismatching Slack app", - }, + type: "internal_server_error", + message: "Failed to deactivate the mismatching Slack app", }); } logger.info( @@ -222,10 +215,8 @@ export async function updateSlackConnector( } return new Err({ - error: { - type: "connector_oauth_target_mismatch", - message: "Cannot change the Slack Team of a Data Source", - }, + type: "connector_oauth_target_mismatch", + message: "Cannot change the Slack Team of a Data Source", }); } diff --git a/connectors/src/lib/error.ts b/connectors/src/lib/error.ts index b348363014f7..dabd29d54412 100644 --- a/connectors/src/lib/error.ts +++ b/connectors/src/lib/error.ts @@ -1,26 +1,3 @@ -export type APIErrorType = - | "internal_server_error" - | "unknown_connector_provider" - | "invalid_request_error" - | "connector_not_found" - | "connector_configuration_not_found" - | "connector_update_error" - | "connector_update_unauthorized" - | "connector_oauth_target_mismatch" - | "not_found" - | "slack_channel_not_found" - | "connector_rate_limit_error"; - -export type APIError = { - type: APIErrorType; - message: string; -}; - -export type APIErrorWithStatusCode = { - api_error: APIError; - status_code: number; -}; - // JS cannot give you any guarantee about the shape of an error you `catch` // eslint-disable-next-line @typescript-eslint/no-explicit-any export function errorFromAny(e: any): Error { diff --git a/connectors/src/logger/withlogging.ts b/connectors/src/logger/withlogging.ts index 31160b3b1aaa..b078e19c658e 100644 --- a/connectors/src/logger/withlogging.ts +++ b/connectors/src/logger/withlogging.ts @@ -1,9 +1,11 @@ +import type { + ConnectorsAPIErrorWithStatusCode, + WithConnectorsAPIErrorReponse, +} from "@dust-tt/types"; import type { Request, Response } from "express"; import StatsD from "hot-shots"; -import type { APIErrorWithStatusCode } from "@connectors/lib/error"; - -import logger from "./logger"; +import logger from "@connectors/logger/logger"; export const statsDClient = new StatsD(); @@ -72,10 +74,10 @@ export const withLogging = (handler: any) => { }; }; -export function apiError( +export function apiError( req: Request, - res: Response, - apiError: APIErrorWithStatusCode, + res: Response>, + apiError: ConnectorsAPIErrorWithStatusCode, error?: Error ): void { logger.error( diff --git a/connectors/src/middleware/auth.ts b/connectors/src/middleware/auth.ts index c33b2f9b758c..59fe081225eb 100644 --- a/connectors/src/middleware/auth.ts +++ b/connectors/src/middleware/auth.ts @@ -1,9 +1,9 @@ +import type { ConnectorsAPIErrorResponse } from "@dust-tt/types"; import crypto from "crypto"; import type { NextFunction, Request, Response } from "express"; import logger from "@connectors/logger/logger"; import { apiError } from "@connectors/logger/withlogging"; -import type { ConnectorsAPIErrorResponse } from "@connectors/types/errors"; const { DUST_CONNECTORS_SECRET, @@ -39,31 +39,52 @@ const _authMiddlewareAPI = ( next: NextFunction ) => { if (!req.headers["authorization"]) { - res.status(401).send({ - error: { + return apiError(req, res, { + api_error: { + type: "authorization_error", message: "Missing Authorization header", }, + status_code: 401, }); - return; } const authorization = req.headers["authorization"]; if (typeof authorization !== "string") { - return res.status(401).send({ - error: { message: "Invalid Authorization header. Should be a string" }, + return apiError(req, res, { + api_error: { + type: "authorization_error", + message: "Invalid Authorization header. Should be a string", + }, + status_code: 401, }); } if (authorization.split(" ")[0] !== "Bearer") { - return res - .status(401) - .send({ error: { message: "Invalid Authorization header" } }); + return apiError(req, res, { + api_error: { + type: "authorization_error", + message: "Invalid Authorization header", + }, + status_code: 401, + }); } const secret = authorization.split(" ")[1]; if (!secret) { - return res.status(401).send({ error: { message: "Missing API key" } }); + return apiError(req, res, { + api_error: { + type: "authorization_error", + message: "Missing API key", + }, + status_code: 401, + }); } if (secret !== DUST_CONNECTORS_SECRET) { - return res.status(401).send({ error: { message: "Invalid API key" } }); + return apiError(req, res, { + api_error: { + type: "authorization_error", + message: "Invalid API key", + }, + status_code: 401, + }); } next(); }; @@ -77,10 +98,12 @@ const _authMiddlewareWebhooks = ( const parts = req.path.split("/"); if (parts.includes(DUST_CONNECTORS_WEBHOOKS_SECRET) === false) { - return res.status(401).send({ - error: { + return apiError(req, res, { + api_error: { + type: "authorization_error", message: "Invalid webhook secret", }, + status_code: 401, }); } } @@ -143,7 +166,7 @@ const _authMiddlewareWebhooksGithub = ( ); return apiError(req, res, { api_error: { - type: "not_found", + type: "connector_not_found", message: "Not found.", }, status_code: 404, diff --git a/connectors/src/types/errors.ts b/connectors/src/types/errors.ts index e7965ea176c1..fee65ef33c57 100644 --- a/connectors/src/types/errors.ts +++ b/connectors/src/types/errors.ts @@ -1,10 +1,3 @@ -export type ConnectorsAPIErrorResponse = { - error: { - message: string; - type?: string; - }; -}; - export function isScheduleAlreadyRunning(err: unknown) { return ( typeof err === "object" && diff --git a/front/admin/cli.ts b/front/admin/cli.ts index bbcd3668f0f2..aeddc8c80bd1 100644 --- a/front/admin/cli.ts +++ b/front/admin/cli.ts @@ -407,7 +407,7 @@ const dataSource = async (command: string, args: parseArgs.ParsedArgs) => { true ); if (connDeleteRes.isErr()) { - throw new Error(connDeleteRes.error.error.message); + throw new Error(connDeleteRes.error.message); } } const coreAPI = new CoreAPI(logger); @@ -613,7 +613,7 @@ const eventSchema = async (command: string, args: parseArgs.ParsedArgs) => { await Promise.all( schemas.map(async (s: EventSchema) => { args.eventSchemaId = s.id; - return await eventSchema("show", args); + return eventSchema("show", args); }) ); return; diff --git a/front/lib/api/data_sources.ts b/front/lib/api/data_sources.ts index c8ea63aa5b06..ac87135d18a2 100644 --- a/front/lib/api/data_sources.ts +++ b/front/lib/api/data_sources.ts @@ -208,10 +208,10 @@ export async function deleteDataSource( if (connDeleteRes.isErr()) { // If we get a not found we proceed with the deletion of the data source. This will enable // us to retry deletion of the data source if it fails at the Core level. - if (connDeleteRes.error.error.type !== "connector_not_found") { + if (connDeleteRes.error.type !== "connector_not_found") { return new Err({ type: "internal_server_error", - message: `Error deleting connector: ${connDeleteRes.error.error.message}`, + message: `Error deleting connector`, connectors_error: connDeleteRes.error, }); } diff --git a/front/pages/api/w/[wId]/data_sources/[name]/managed/bot_enabled.ts b/front/pages/api/w/[wId]/data_sources/[name]/managed/bot_enabled.ts index 28cdd99fa595..0df8b11d1de8 100644 --- a/front/pages/api/w/[wId]/data_sources/[name]/managed/bot_enabled.ts +++ b/front/pages/api/w/[wId]/data_sources/[name]/managed/bot_enabled.ts @@ -85,7 +85,8 @@ async function handler( status_code: 404, api_error: { type: "data_source_error", - message: `Failed to retrieve bot enablement: ${botEnabledRes.error.error.message}`, + message: `Failed to retrieve bot enablement settings.`, + connectors_error: botEnabledRes.error, }, }); } @@ -130,7 +131,8 @@ async function handler( status_code: 400, api_error: { type: "data_source_error", - message: setBotEnabledRes.error.error.message, + message: "Failed to edit the (bot) permissions of the data source.", + connectors_error: setBotEnabledRes.error, }, }); } diff --git a/front/pages/api/w/[wId]/data_sources/[name]/managed/permissions/index.ts b/front/pages/api/w/[wId]/data_sources/[name]/managed/permissions/index.ts index 168a600746d8..89e05d2685d9 100644 --- a/front/pages/api/w/[wId]/data_sources/[name]/managed/permissions/index.ts +++ b/front/pages/api/w/[wId]/data_sources/[name]/managed/permissions/index.ts @@ -226,7 +226,8 @@ async function handler( status_code: 500, api_error: { type: "internal_server_error", - message: connectorsRes.error.error.message, + message: "Failed to set the permissions of the data source.", + connectors_error: connectorsRes.error, }, }); } diff --git a/front/pages/api/w/[wId]/data_sources/[name]/managed/update.ts b/front/pages/api/w/[wId]/data_sources/[name]/managed/update.ts index 1e13472499d9..66c8840c47db 100644 --- a/front/pages/api/w/[wId]/data_sources/[name]/managed/update.ts +++ b/front/pages/api/w/[wId]/data_sources/[name]/managed/update.ts @@ -1,4 +1,3 @@ -import type { ConnectorsAPIErrorResponse } from "@dust-tt/types"; import type { ReturnedAPIErrorType } from "@dust-tt/types"; import { ConnectorsAPI } from "@dust-tt/types"; import { isLeft } from "fp-ts/lib/Either"; @@ -106,14 +105,14 @@ async function handler( }); if (updateRes.isErr()) { - const errorRes = updateRes as { error: ConnectorsAPIErrorResponse }; - const error = errorRes.error.error; - - if (error.type === "connector_oauth_target_mismatch") { + if (updateRes.error.type === "connector_oauth_target_mismatch") { return apiError(req, res, { api_error: { - type: error.type, - message: error.message, + type: updateRes.error.type, + // The error message is meant to be user friendly and explannative, customized for the + // connection being updated. + message: `OAuth mismatch: ${updateRes.error.message}`, + connectors_error: updateRes.error, }, status_code: 401, }); @@ -122,7 +121,8 @@ async function handler( status_code: 500, api_error: { type: "internal_server_error", - message: `Could not update the connector: ${error.message}`, + message: `Could not update the connector`, + connectors_error: updateRes.error, }, }); } diff --git a/front/pages/w/[wId]/builder/data-sources/managed.tsx b/front/pages/w/[wId]/builder/data-sources/managed.tsx index b9bb11b40a83..76a5a53de268 100644 --- a/front/pages/w/[wId]/builder/data-sources/managed.tsx +++ b/front/pages/w/[wId]/builder/data-sources/managed.tsx @@ -134,7 +134,7 @@ export const getServerSideProps: GetServerSideProps<{ provider: mds.connectorProvider, connector: null, fetchConnectorError: true, - fetchConnectorErrorMessage: statusRes.error.error.message, + fetchConnectorErrorMessage: statusRes.error.message, }; } return { diff --git a/types/src/connectors/api.ts b/types/src/connectors/api.ts new file mode 100644 index 000000000000..fd7547d9af49 --- /dev/null +++ b/types/src/connectors/api.ts @@ -0,0 +1,42 @@ +export type ConnectorsAPIErrorType = + | "authorization_error" + | "not_found" + | "internal_server_error" + | "unexpected_error_format" + | "unexpected_response_format" + | "unknown_connector_provider" + | "invalid_request_error" + | "connector_not_found" + | "connector_configuration_not_found" + | "connector_update_error" + | "connector_update_unauthorized" + | "connector_oauth_target_mismatch" + | "slack_channel_not_found" + | "connector_rate_limit_error"; + +export type ConnectorsAPIError = { + type: ConnectorsAPIErrorType; + message: string; +}; + +export type ConnectorsAPIErrorResponse = { + error: ConnectorsAPIError; +}; + +export type ConnectorsAPIErrorWithStatusCode = { + api_error: ConnectorsAPIError; + status_code: number; +}; + +export type WithConnectorsAPIErrorReponse = T | ConnectorsAPIErrorResponse; + +export function isConnectorsAPIError(obj: unknown): obj is ConnectorsAPIError { + return ( + typeof obj === "object" && + obj !== null && + "message" in obj && + typeof obj.message === "string" && + "type" in obj && + typeof obj.type === "string" + ); +} diff --git a/types/src/front/lib/connectors_api.ts b/types/src/front/lib/connectors_api.ts index e869ee2453b9..99c7721bc57d 100644 --- a/types/src/front/lib/connectors_api.ts +++ b/types/src/front/lib/connectors_api.ts @@ -1,20 +1,14 @@ +import { ConnectorsAPIError, isConnectorsAPIError } from "../../connectors/api"; import { ConnectorProvider } from "../../front/data_source"; import { Err, Ok, Result } from "../../front/lib/result"; import { LoggerInterface } from "../../shared/logger"; -export type ConnectorsAPIErrorResponse = { - error: { - message: string; - type?: string; - }; -}; - const { CONNECTORS_API = "http://127.0.0.1:3002", DUST_CONNECTORS_SECRET = "", } = process.env; -export type ConnectorsAPIResponse = Result; +export type ConnectorsAPIResponse = Result; export type ConnectorSyncStatus = "succeeded" | "failed"; export type ConnectorErrorType = "oauth_token_revoked"; @@ -406,54 +400,56 @@ export class ConnectorsAPI { async _resultFromResponse( response: Response ): Promise> { + const parseError = async (response: Response, error: SyntaxError) => { + const text = await response.text(); + + const err: ConnectorsAPIError = { + type: "unexpected_response_format", + message: `Unexpected response format from ConnectorAPI: ${error}`, + }; + this._logger.error( + { error: err, parseError: error, status: response.status }, + "ConnectorsAPI error" + ); + this._logger.error( + { error: err, parseError: error, status: response.status }, + `ConnectorsAPI error, raw response text: ${text}` + ); + return new Err(err); + }; + if (!response.ok) { - if (response.headers.get("Content-Type") === "application/json") { - const jsonError = await response.json(); - this._logger.error( - { jsonError }, - "Unexpected response from ConnectorAPI" - ); - return new Err(jsonError); - } else { - const textError = await response.text(); - try { - const errorResponse = JSON.parse(textError); - const errorMessage = errorResponse?.error?.message; - const errorType = errorResponse?.error?.type; - - if ( - typeof errorMessage !== "string" || - typeof errorType !== "string" - ) { - throw new Error("Unexpected response from ConnectorAPI"); - } - - return new Err({ - error: { - message: errorMessage, - type: errorType, - }, - }); - } catch (error) { + try { + const json = await response.json(); + const err = json?.error; + + if (isConnectorsAPIError(err)) { + this._logger.error( + { error: err, status: response.status }, + "ConnectorAPI error" + ); + return new Err(err); + } else { + const err: ConnectorsAPIError = { + type: "unexpected_error_format", + message: "Unexpected error format from ConnectorAPI", + }; this._logger.error( - { - statusCode: response.status, - error, - textError, - }, - "Unexpected response from ConnectorAPI" + { error: err, json, status: response.status }, + "ConnectorsAPI error" ); - return new Err({ - error: { - message: `Unexpected response status: ${response.status} ${response.statusText}`, - type: "unexpected_response", - }, - }); + return new Err(err); } + } catch (e) { + return parseError(response, e as SyntaxError); } } - const jsonResponse = await response.json(); - return new Ok(jsonResponse); + try { + const json = await response.json(); + return new Ok(json); + } catch (e) { + return parseError(response, e as SyntaxError); + } } } diff --git a/types/src/front/lib/error.ts b/types/src/front/lib/error.ts index 92ef35c230cf..169cbd3440fd 100644 --- a/types/src/front/lib/error.ts +++ b/types/src/front/lib/error.ts @@ -1,4 +1,4 @@ -import { ConnectorsAPIErrorResponse } from "./connectors_api"; +import { ConnectorsAPIError } from "../../connectors/api"; import { CoreAPIErrorResponse } from "./core_api"; export type InternalErrorWithStatusCode = { @@ -57,7 +57,7 @@ export type APIError = { data_source_error?: CoreAPIErrorResponse; run_error?: CoreAPIErrorResponse; app_error?: CoreAPIErrorResponse; - connectors_error?: ConnectorsAPIErrorResponse; + connectors_error?: ConnectorsAPIError; }; export type ReturnedAPIErrorType = { diff --git a/types/src/index.ts b/types/src/index.ts index 71e3c068e8ce..58eb20e77be2 100644 --- a/types/src/index.ts +++ b/types/src/index.ts @@ -1,3 +1,4 @@ +export * from "./connectors/api"; export * from "./connectors/api_handlers/create_connector"; export * from "./core/data_source"; export * from "./front/api_handlers/internal/agent_configuration";