diff --git a/packages/connectors/connector-aliyun-dm/src/index.ts b/packages/connectors/connector-aliyun-dm/src/index.ts index a243f94bb..ed743cf4e 100644 --- a/packages/connectors/connector-aliyun-dm/src/index.ts +++ b/packages/connectors/connector-aliyun-dm/src/index.ts @@ -74,7 +74,10 @@ const sendMessage = assert( typeof rawBody === 'string', - new ConnectorError(ConnectorErrorCodes.InvalidResponse) + new ConnectorError( + ConnectorErrorCodes.InvalidResponse, + `Invalid response raw body type: ${typeof rawBody}` + ) ); errorHandler(rawBody); diff --git a/packages/connectors/connector-aliyun-sms/src/index.ts b/packages/connectors/connector-aliyun-sms/src/index.ts index 932c5f87d..ebc9db7a5 100644 --- a/packages/connectors/connector-aliyun-sms/src/index.ts +++ b/packages/connectors/connector-aliyun-sms/src/index.ts @@ -41,7 +41,10 @@ const sendMessage = assert( template, - new ConnectorError(ConnectorErrorCodes.TemplateNotFound, `Cannot find template!`) + new ConnectorError( + ConnectorErrorCodes.TemplateNotFound, + `Cannot find template for type: ${type}` + ) ); try { @@ -56,37 +59,49 @@ const sendMessage = accessKeySecret ); - const { body: rawBody } = httpResponse; + const { Code, Message, ...rest } = parseResponseString(httpResponse.body); - const { Code, Message, ...rest } = parseResponseString(rawBody); + assert( + Code === 'OK', + new ConnectorError( + /** + * See https://help.aliyun.com/document_detail/101347.html for more details. + * Some errors (like rate limit) can be addressed by end users. + */ + Code === 'isv.BUSINESS_LIMIT_CONTROL' + ? ConnectorErrorCodes.RateLimitExceeded + : ConnectorErrorCodes.General, + { + errorDescription: Message, + Code, + ...rest, + } + ) + ); - if (Code !== 'OK') { + return { Code, Message, ...rest }; + } catch (error: unknown) { + if (error instanceof HTTPError) { + const { + response: { body: rawBody }, + } = error; + + assert( + typeof rawBody === 'string', + new ConnectorError( + ConnectorErrorCodes.InvalidResponse, + `Invalid response raw body type: ${typeof rawBody}` + ) + ); + + const { Message, ...rest } = parseResponseString(rawBody); throw new ConnectorError(ConnectorErrorCodes.General, { errorDescription: Message, - Code, ...rest, }); } - return httpResponse; - } catch (error: unknown) { - if (!(error instanceof HTTPError)) { - throw error; - } - - const { - response: { body: rawBody }, - } = error; - - assert(typeof rawBody === 'string', new ConnectorError(ConnectorErrorCodes.InvalidResponse)); - - const { Code, Message, ...rest } = parseResponseString(rawBody); - - throw new ConnectorError(ConnectorErrorCodes.General, { - errorDescription: Message, - Code, - ...rest, - }); + throw error; } }; diff --git a/packages/connectors/connector-aws-ses/src/index.ts b/packages/connectors/connector-aws-ses/src/index.ts index cef7e6756..63736ef4f 100644 --- a/packages/connectors/connector-aws-ses/src/index.ts +++ b/packages/connectors/connector-aws-ses/src/index.ts @@ -1,6 +1,6 @@ import { assert } from '@silverhand/essentials'; -import type { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2'; +import type { SESv2Client, SendEmailCommand, SendEmailCommandOutput } from '@aws-sdk/client-sesv2'; import { SESv2ServiceException } from '@aws-sdk/client-sesv2'; import type { CreateConnector, @@ -42,18 +42,19 @@ const sendMessage = const command: SendEmailCommand = makeCommand(config, emailContent, to); try { - const response = await client.send(command); + const response: SendEmailCommandOutput = await client.send(command); - if (response.$metadata.httpStatusCode !== 200) { - throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, { response }); - } + assert( + response.$metadata.httpStatusCode === 200, + new ConnectorError(ConnectorErrorCodes.InvalidResponse, response) + ); return response.MessageId; } catch (error: unknown) { if (error instanceof SESv2ServiceException) { - const { message } = error; - throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, message); + throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, error.message); } + throw error; } }; diff --git a/packages/connectors/connector-sendgrid-email/src/index.ts b/packages/connectors/connector-sendgrid-email/src/index.ts index 9d5f4ccc1..482965598 100644 --- a/packages/connectors/connector-sendgrid-email/src/index.ts +++ b/packages/connectors/connector-sendgrid-email/src/index.ts @@ -75,9 +75,13 @@ const sendMessage = const { response: { body: rawBody }, } = error; + assert( typeof rawBody === 'string', - new ConnectorError(ConnectorErrorCodes.InvalidResponse) + new ConnectorError( + ConnectorErrorCodes.InvalidResponse, + `Invalid response raw body type: ${typeof rawBody}` + ) ); throw new ConnectorError(ConnectorErrorCodes.General, rawBody); diff --git a/packages/connectors/connector-twilio-sms/src/index.ts b/packages/connectors/connector-twilio-sms/src/index.ts index ccad11830..f69f74c56 100644 --- a/packages/connectors/connector-twilio-sms/src/index.ts +++ b/packages/connectors/connector-twilio-sms/src/index.ts @@ -60,7 +60,10 @@ const sendMessage = } = error; assert( typeof rawBody === 'string', - new ConnectorError(ConnectorErrorCodes.InvalidResponse) + new ConnectorError( + ConnectorErrorCodes.InvalidResponse, + `Invalid response raw body type: ${typeof rawBody}` + ) ); throw new ConnectorError(ConnectorErrorCodes.General, rawBody); diff --git a/packages/core/src/middleware/koa-connector-error-handler.test.ts b/packages/core/src/middleware/koa-connector-error-handler.test.ts index cec0e796a..f379c7549 100644 --- a/packages/core/src/middleware/koa-connector-error-handler.test.ts +++ b/packages/core/src/middleware/koa-connector-error-handler.test.ts @@ -187,6 +187,24 @@ describe('koaConnectorErrorHandler middleware', () => { ); }); + it('Rate Limit Exceeded', async () => { + const message = 'Mock Rate Limit Exceeded'; + const error = new ConnectorError(ConnectorErrorCodes.RateLimitExceeded, message); + next.mockImplementationOnce(() => { + throw error; + }); + + await expect(koaConnectorErrorHandler()(ctx, next)).rejects.toMatchError( + new RequestError( + { + code: 'connector.rate_limit_exceeded', + status: 429, + }, + { message } + ) + ); + }); + it('General connector errors with string type messages', async () => { const message = 'Mock General connector errors'; const error = new ConnectorError(ConnectorErrorCodes.General, message); diff --git a/packages/core/src/middleware/koa-connector-error-handler.ts b/packages/core/src/middleware/koa-connector-error-handler.ts index 311e9b239..5bad6e65a 100644 --- a/packages/core/src/middleware/koa-connector-error-handler.ts +++ b/packages/core/src/middleware/koa-connector-error-handler.ts @@ -52,6 +52,10 @@ export default function koaConnectorErrorHandler(): Middleware throw new RequestError({ code: `connector.${code}`, status: 501 }, data); } + case ConnectorErrorCodes.RateLimitExceeded: { + throw new RequestError({ code: `connector.${code}`, status: 429 }, data); + } + default: { throw new RequestError( { diff --git a/packages/phrases/src/locales/de/errors/connector.ts b/packages/phrases/src/locales/de/errors/connector.ts index 478d9b6a0..00a6019fb 100644 --- a/packages/phrases/src/locales/de/errors/connector.ts +++ b/packages/phrases/src/locales/de/errors/connector.ts @@ -12,6 +12,7 @@ const connector = { invalid_response: 'Die Antwort des Connectors ist ungültig.', template_not_found: 'Die richtige Vorlage in der Connector-Konfiguration konnte nicht gefunden werden.', + rate_limit_exceeded: 'Auslöser-Rate-Limit. Bitte versuchen Sie es später erneut.', not_implemented: '{{method}}: wurde noch nicht implementiert.', social_invalid_access_token: 'Der Access Token des Connectors ist ungültig.', invalid_auth_code: 'Der Authentifizierungscode des Connectors ist ungültig.', @@ -38,5 +39,4 @@ const connector = { cannot_overwrite_metadata_for_non_standard_connector: "Die 'Metadaten' dieses Connectors können nicht überschrieben werden.", }; - export default connector; diff --git a/packages/phrases/src/locales/en/errors/connector.ts b/packages/phrases/src/locales/en/errors/connector.ts index 43f16fdc3..cc555dbeb 100644 --- a/packages/phrases/src/locales/en/errors/connector.ts +++ b/packages/phrases/src/locales/en/errors/connector.ts @@ -10,6 +10,7 @@ const connector = { invalid_config: "The connector's config is invalid.", invalid_response: "The connector's response is invalid.", template_not_found: 'Unable to find correct template in connector config.', + rate_limit_exceeded: 'Trigger rate limit. Please try again later.', not_implemented: '{{method}}: has not been implemented yet.', social_invalid_access_token: "The connector's access token is invalid.", invalid_auth_code: "The connector's auth code is invalid.", diff --git a/packages/phrases/src/locales/es/errors/connector.ts b/packages/phrases/src/locales/es/errors/connector.ts index d45a99c85..13c48f4b3 100644 --- a/packages/phrases/src/locales/es/errors/connector.ts +++ b/packages/phrases/src/locales/es/errors/connector.ts @@ -11,6 +11,7 @@ const connector = { invalid_response: 'La respuesta del conector es inválida.', template_not_found: 'No se puede encontrar la plantilla correcta en la configuración del conector.', + rate_limit_exceeded: 'Límite de frecuencia activado. Por favor, inténtalo de nuevo más tarde.', not_implemented: '{{method}}: aún no se ha implementado.', social_invalid_access_token: 'El token de acceso del conector es inválido.', invalid_auth_code: 'El código de autenticación del conector es inválido.', diff --git a/packages/phrases/src/locales/fr/errors/connector.ts b/packages/phrases/src/locales/fr/errors/connector.ts index ccce4fa1d..620aea456 100644 --- a/packages/phrases/src/locales/fr/errors/connector.ts +++ b/packages/phrases/src/locales/fr/errors/connector.ts @@ -10,6 +10,7 @@ const connector = { invalid_config: "La configuration du connecteur n'est pas valide.", invalid_response: "La réponse du connecteur n'est pas valide.", template_not_found: 'Impossible de trouver le bon modèle dans la configuration du connecteur.', + rate_limit_exceeded: 'Limite de taux déclenchée. Veuillez réessayer plus tard.', not_implemented: "{{method}} : n'a pas encore été mis en œuvre.", social_invalid_access_token: "Le jeton d'accès du connecteur n'est pas valide.", invalid_auth_code: "Le code d'authentification du connecteur n'est pas valide.", diff --git a/packages/phrases/src/locales/it/errors/connector.ts b/packages/phrases/src/locales/it/errors/connector.ts index 1ee61f08b..6fab27c8d 100644 --- a/packages/phrases/src/locales/it/errors/connector.ts +++ b/packages/phrases/src/locales/it/errors/connector.ts @@ -11,6 +11,7 @@ const connector = { invalid_response: 'La risposta del connettore non è valida.', template_not_found: 'Impossibile trovare il modello corretto nella configurazione del connettore.', + rate_limit_exceeded: 'Limite di frequenza attivata. Riprova più tardi.', not_implemented: '{{method}}: non è stato ancora implementato.', social_invalid_access_token: 'Il token di accesso del connettore non è valido.', invalid_auth_code: 'Il codice di autenticazione del connettore non è valido.', diff --git a/packages/phrases/src/locales/ja/errors/connector.ts b/packages/phrases/src/locales/ja/errors/connector.ts index ceb6f2d4a..c109cc6c7 100644 --- a/packages/phrases/src/locales/ja/errors/connector.ts +++ b/packages/phrases/src/locales/ja/errors/connector.ts @@ -10,6 +10,7 @@ const connector = { invalid_config: 'コネクタの設定が無効です。', invalid_response: 'コネクタのレスポンスが無効です。', template_not_found: 'コネクタ構成から正しいテンプレートを見つけることができませんでした。', + rate_limit_exceeded: 'トリガーレート制限。後でもう一度お試しください。', not_implemented: '{{method}}:まだ実装されていません。', social_invalid_access_token: 'コネクタのアクセストークンが無効です。', invalid_auth_code: 'コネクタの認証コードが無効です。', diff --git a/packages/phrases/src/locales/ko/errors/connector.ts b/packages/phrases/src/locales/ko/errors/connector.ts index ba0cf54bd..3a742770d 100644 --- a/packages/phrases/src/locales/ko/errors/connector.ts +++ b/packages/phrases/src/locales/ko/errors/connector.ts @@ -10,6 +10,7 @@ const connector = { invalid_config: '연동 설정이 유효하지 않아요.', invalid_response: '연동 응답이 유효하지 않아요.', template_not_found: '연동 예제 설정을 찾을 수 없어요.', + rate_limit_exceeded: '트리거 주기 제한. 나중에 다시 시도하세요.', not_implemented: '{{method}}은 아직 구현되지 않았어요.', social_invalid_access_token: '연동 서비스의 Access 토큰이 유효하지 않아요.', invalid_auth_code: '연동 서비스의 Auth 코드가 유효하지 않아요.', diff --git a/packages/phrases/src/locales/pl-pl/errors/connector.ts b/packages/phrases/src/locales/pl-pl/errors/connector.ts index a7fc2fbc7..efdc2bdf7 100644 --- a/packages/phrases/src/locales/pl-pl/errors/connector.ts +++ b/packages/phrases/src/locales/pl-pl/errors/connector.ts @@ -11,6 +11,7 @@ const connector = { invalid_config: 'Konfiguracja łącznika jest nieprawidłowa.', invalid_response: 'Odpowiedź łącznika jest nieprawidłowa.', template_not_found: 'Nie można znaleźć poprawnego szablonu w konfiguracji łącznika.', + rate_limit_exceeded: 'Ograniczenie szybkości wywołań. Spróbuj ponownie później.', not_implemented: '{{method}}: jeszcze nie zaimplementowano.', social_invalid_access_token: 'Token dostępu łącznika jest nieprawidłowy.', invalid_auth_code: 'Kod autoryzacji łącznika jest nieprawidłowy.', diff --git a/packages/phrases/src/locales/pt-br/errors/connector.ts b/packages/phrases/src/locales/pt-br/errors/connector.ts index 8f30b3175..2856e4b7c 100644 --- a/packages/phrases/src/locales/pt-br/errors/connector.ts +++ b/packages/phrases/src/locales/pt-br/errors/connector.ts @@ -10,6 +10,7 @@ const connector = { invalid_config: 'A configuração do conector é inválida.', invalid_response: 'A resposta do conector é inválida.', template_not_found: 'Não foi possível encontrar o modelo correto na configuração do conector.', + rate_limit_exceeded: 'Limite de taxa de acionamento. Tente novamente mais tarde.', not_implemented: '{{method}}: ainda não foi implementado.', social_invalid_access_token: 'O token de acesso do conector é inválido.', invalid_auth_code: 'O código de autenticação do conector é inválido.', diff --git a/packages/phrases/src/locales/pt-pt/errors/connector.ts b/packages/phrases/src/locales/pt-pt/errors/connector.ts index 720027c5f..6672b04b8 100644 --- a/packages/phrases/src/locales/pt-pt/errors/connector.ts +++ b/packages/phrases/src/locales/pt-pt/errors/connector.ts @@ -10,6 +10,7 @@ const connector = { invalid_config: 'A configuração do conector é inválida.', invalid_response: 'A resposta do conector é inválida.', template_not_found: 'Não foi possível encontrar o modelo correto na configuração do conector.', + rate_limit_exceeded: 'Limite de taxa de ativação. Por favor, tente novamente mais tarde.', not_implemented: '{{method}}: ainda não foi implementado.', social_invalid_access_token: 'O token de acesso do conector é inválido.', invalid_auth_code: 'O código de autenticação do conector é inválido.', diff --git a/packages/phrases/src/locales/ru/errors/connector.ts b/packages/phrases/src/locales/ru/errors/connector.ts index a397604f0..9acc11dd1 100644 --- a/packages/phrases/src/locales/ru/errors/connector.ts +++ b/packages/phrases/src/locales/ru/errors/connector.ts @@ -10,6 +10,7 @@ const connector = { invalid_config: 'Конфигурация коннектора недействительна.', invalid_response: 'Ответ коннектора недействителен.', template_not_found: 'Невозможно найти правильный шаблон в конфигурации коннектора.', + rate_limit_exceeded: 'Превышен лимит запросов. Пожалуйста, попробуйте позже.', not_implemented: '{{method}}: еще не реализован.', social_invalid_access_token: 'Токен доступа коннектора недействителен.', invalid_auth_code: 'Код аутентификации коннектора недействителен.', @@ -34,4 +35,5 @@ const connector = { cannot_overwrite_metadata_for_non_standard_connector: 'Метаданные этого коннектора не могут быть перезаписаны.', }; + export default connector; diff --git a/packages/phrases/src/locales/tr-tr/errors/connector.ts b/packages/phrases/src/locales/tr-tr/errors/connector.ts index a16657f34..afb2c86ed 100644 --- a/packages/phrases/src/locales/tr-tr/errors/connector.ts +++ b/packages/phrases/src/locales/tr-tr/errors/connector.ts @@ -10,6 +10,7 @@ const connector = { invalid_config: 'Bağlayıcının ayarları geçersiz.', invalid_response: 'Bağlayıcının yanıtı geçersiz.', template_not_found: 'Bağlayıcı yapılandırmasında doğru şablon bulunamıyor.', + rate_limit_exceeded: 'Tetikleyici oran sınırına ulaşıldı. Lütfen daha sonra tekrar deneyin.', not_implemented: '{{method}}: henüz uygulanmadı.', social_invalid_access_token: 'Bağlayıcının erişim tokenı geçersiz.', invalid_auth_code: 'Bağlayıcının yetki kodu geçersiz.', diff --git a/packages/phrases/src/locales/zh-cn/errors/connector.ts b/packages/phrases/src/locales/zh-cn/errors/connector.ts index d518e43f2..0e5b6cf45 100644 --- a/packages/phrases/src/locales/zh-cn/errors/connector.ts +++ b/packages/phrases/src/locales/zh-cn/errors/connector.ts @@ -10,6 +10,7 @@ const connector = { invalid_config: '连接器配置错误', invalid_response: '连接器错误响应', template_not_found: '无法从连接器配置中找到对应的模板', + rate_limit_exceeded: '触发速率限制。请稍后再试。', not_implemented: '方法 {{method}} 尚未实现', social_invalid_access_token: '当前连接器的 access_token 无效', invalid_auth_code: '当前连接器的授权码无效', diff --git a/packages/phrases/src/locales/zh-hk/errors/connector.ts b/packages/phrases/src/locales/zh-hk/errors/connector.ts index be673b475..d77f7ca8c 100644 --- a/packages/phrases/src/locales/zh-hk/errors/connector.ts +++ b/packages/phrases/src/locales/zh-hk/errors/connector.ts @@ -10,6 +10,7 @@ const connector = { invalid_config: '連接器配置錯誤', invalid_response: '連接器錯誤響應', template_not_found: '無法從連接器配置中找到對應的模板', + rate_limit_exceeded: '觸發速率限制。請稍後再試。', not_implemented: '方法 {{method}} 尚未實現', social_invalid_access_token: '當前連接器的 access_token 無效', invalid_auth_code: '當前連接器的授權碼無效', diff --git a/packages/phrases/src/locales/zh-tw/errors/connector.ts b/packages/phrases/src/locales/zh-tw/errors/connector.ts index 639299516..f3d3b9f9a 100644 --- a/packages/phrases/src/locales/zh-tw/errors/connector.ts +++ b/packages/phrases/src/locales/zh-tw/errors/connector.ts @@ -10,6 +10,7 @@ const connector = { invalid_config: '連接器配置錯誤', invalid_response: '連接器錯誤響應', template_not_found: '無法從連接器配置中找到對應的模板', + rate_limit_exceeded: '觸發速率限制。請稍後再試。', not_implemented: '方法 {{method}} 尚未實現', social_invalid_access_token: '當前連接器的 access_token 無效', invalid_auth_code: '當前連接器的授權碼無效', diff --git a/packages/toolkit/connector-kit/src/types.ts b/packages/toolkit/connector-kit/src/types.ts index f167d9dcf..ce225e288 100644 --- a/packages/toolkit/connector-kit/src/types.ts +++ b/packages/toolkit/connector-kit/src/types.ts @@ -49,6 +49,7 @@ export enum ConnectorErrorCodes { InvalidConfig = 'invalid_config', InvalidResponse = 'invalid_response', TemplateNotFound = 'template_not_found', + RateLimitExceeded = 'rate_limit_exceeded', NotImplemented = 'not_implemented', SocialAuthCodeInvalid = 'social_auth_code_invalid', SocialAccessTokenInvalid = 'social_invalid_access_token',