From 395526f59b380aa94caa6a307ffdf7e2f4ca2ef1 Mon Sep 17 00:00:00 2001 From: Himanshu Dixit Date: Wed, 25 Dec 2024 11:07:34 +0530 Subject: [PATCH 1/3] feat: update be error --- js/src/sdk/utils/error.ts | 9 +-- js/src/sdk/utils/errors/src/constants.ts | 2 +- js/src/sdk/utils/errors/src/formatter.ts | 77 ++++++++---------------- 3 files changed, 30 insertions(+), 58 deletions(-) diff --git a/js/src/sdk/utils/error.ts b/js/src/sdk/utils/error.ts index 45bc0aa0a08..cdfa9d8c849 100644 --- a/js/src/sdk/utils/error.ts +++ b/js/src/sdk/utils/error.ts @@ -3,7 +3,7 @@ import { ZodError } from "zod"; import { ComposioError } from "./errors/src/composioError"; import { BASE_ERROR_CODE_INFO, - BE_STATUS_CODE_TO_SDK_ERROR_CODES, + API_TO_SDK_ERROR_CODE, COMPOSIO_SDK_ERROR_CODES, } from "./errors/src/constants"; import { @@ -124,15 +124,12 @@ export class CEG { static throwAPIError(error: AxiosError) { const statusCode = error?.response?.status || null; const errorCode = statusCode - ? BE_STATUS_CODE_TO_SDK_ERROR_CODES[statusCode] || + ? API_TO_SDK_ERROR_CODE[statusCode] || COMPOSIO_SDK_ERROR_CODES.BACKEND.UNKNOWN : COMPOSIO_SDK_ERROR_CODES.BACKEND.UNKNOWN; - const predefinedError = BASE_ERROR_CODE_INFO[errorCode]; - + const errorDetails = getAPIErrorDetails( - errorCode, error as AxiosError, - predefinedError ); const metadata = generateMetadataFromAxiosError(error); diff --git a/js/src/sdk/utils/errors/src/constants.ts b/js/src/sdk/utils/errors/src/constants.ts index 1eb124b0a48..e5eb47acb35 100644 --- a/js/src/sdk/utils/errors/src/constants.ts +++ b/js/src/sdk/utils/errors/src/constants.ts @@ -95,7 +95,7 @@ export const BASE_ERROR_CODE_INFO = { }, }; -export const BE_STATUS_CODE_TO_SDK_ERROR_CODES = { +export const API_TO_SDK_ERROR_CODE = { 400: COMPOSIO_SDK_ERROR_CODES.BACKEND.BAD_REQUEST, 401: COMPOSIO_SDK_ERROR_CODES.BACKEND.UNAUTHORIZED, 404: COMPOSIO_SDK_ERROR_CODES.BACKEND.NOT_FOUND, diff --git a/js/src/sdk/utils/errors/src/formatter.ts b/js/src/sdk/utils/errors/src/formatter.ts index fb97ed5fcc4..59dbe36adba 100644 --- a/js/src/sdk/utils/errors/src/formatter.ts +++ b/js/src/sdk/utils/errors/src/formatter.ts @@ -1,10 +1,12 @@ import { AxiosError } from "axios"; -import { COMPOSIO_SDK_ERROR_CODES } from "./constants"; +import { BASE_ERROR_CODE_INFO, API_TO_SDK_ERROR_CODE, COMPOSIO_SDK_ERROR_CODES } from "./constants"; export interface ErrorResponseData { - message: string; - error: string; - errors?: Record[]; + error: { + type: string; + name: string; + message: string; + }; } interface ErrorDetails { @@ -15,72 +17,45 @@ interface ErrorDetails { } export const getAPIErrorDetails = ( - errorKey: string, axiosError: AxiosError, - predefinedError: Record ): ErrorDetails => { + const statusCode = axiosError.response?.status; + const errorCode = statusCode ? API_TO_SDK_ERROR_CODE[statusCode] : COMPOSIO_SDK_ERROR_CODES.BACKEND.UNKNOWN; + const predefinedError = BASE_ERROR_CODE_INFO[errorCode]; + const defaultErrorDetails = { message: axiosError.message, - description: - axiosError.response?.data?.message || - axiosError.response?.data?.error || - axiosError.message, - possibleFix: - "Please check the network connection, request parameters, and ensure the API endpoint is correct.", + description: axiosError.response?.data?.error?.message || axiosError.response?.data?.error || axiosError.message, + possibleFix: "Please check the network connection, request parameters, and ensure the API endpoint is correct.", }; const metadata = generateMetadataFromAxiosError(axiosError); - switch (errorKey) { + + const errorNameFromBE = axiosError.response?.data?.error?.name; + const errorTypeFromBE = axiosError.response?.data?.error?.type; + const errorMessage = axiosError.response?.data?.error?.message; + const genericMessage = `${errorNameFromBE} ${errorTypeFromBE ? `- ${errorTypeFromBE}` : ""} on ${axiosError.config?.baseURL! + axiosError.config?.url!}`; + + switch (errorCode) { case COMPOSIO_SDK_ERROR_CODES.BACKEND.NOT_FOUND: case COMPOSIO_SDK_ERROR_CODES.BACKEND.UNAUTHORIZED: case COMPOSIO_SDK_ERROR_CODES.BACKEND.SERVER_ERROR: case COMPOSIO_SDK_ERROR_CODES.BACKEND.SERVER_UNAVAILABLE: case COMPOSIO_SDK_ERROR_CODES.BACKEND.RATE_LIMIT: - return { - message: `${predefinedError.message || axiosError.message} for ${axiosError.config?.baseURL! + axiosError.config?.url!}`, - description: (axiosError.response?.data?.message! || - predefinedError.description) as string, - possibleFix: (predefinedError.possibleFix! || - defaultErrorDetails.possibleFix) as string, - metadata, - }; - - case COMPOSIO_SDK_ERROR_CODES.BACKEND.BAD_REQUEST: - const validationErrors = axiosError.response?.data?.errors; - const formattedErrors = Array.isArray(validationErrors) - ? validationErrors - .map((err) => JSON.stringify(err as Record)) - .join(", ") - : JSON.stringify( - validationErrors as unknown as Record - ); - - return { - message: `Validation Errors while making request to ${axiosError.config?.baseURL! + axiosError.config?.url!}`, - description: `Validation Errors: ${formattedErrors}`, - possibleFix: - "Please check the request parameters and ensure they are correct.", - metadata, - }; - case COMPOSIO_SDK_ERROR_CODES.BACKEND.UNKNOWN: - case COMPOSIO_SDK_ERROR_CODES.COMMON.UNKNOWN: + case COMPOSIO_SDK_ERROR_CODES.BACKEND.BAD_REQUEST: return { - message: `${axiosError.message} for ${axiosError.config?.baseURL! + axiosError.config?.url!}`, - description: (axiosError.response?.data?.message! || - axiosError.response?.data?.error! || - axiosError.message) as string, - possibleFix: "Please contact tech@composio.dev with the error details.", + message: genericMessage, + description: errorMessage || predefinedError.description as string, + possibleFix: predefinedError.possibleFix! || defaultErrorDetails.possibleFix as string, metadata, }; default: return { - message: `${predefinedError.message || axiosError.message} for ${axiosError.config?.baseURL! + axiosError.config?.url!}`, - description: (axiosError.response?.data?.message! || - predefinedError.description) as string, - possibleFix: (predefinedError.possibleFix! || - defaultErrorDetails.possibleFix) as string, + message: genericMessage, + description: errorMessage || predefinedError.description as string, + possibleFix: predefinedError.possibleFix! || defaultErrorDetails.possibleFix as string, metadata, }; } From 32afa2b13746f442859a448655b75e72efdd3e0f Mon Sep 17 00:00:00 2001 From: Himanshu Dixit Date: Wed, 25 Dec 2024 12:02:40 +0530 Subject: [PATCH 2/3] feat: update error according to backend --- js/src/sdk/index.spec.ts | 72 +++++++++++++++++------- js/src/sdk/index.ts | 2 + js/src/sdk/models/backendClient.ts | 4 ++ js/src/sdk/utils/composioContext.ts | 1 + js/src/sdk/utils/error.ts | 6 +- js/src/sdk/utils/errors/src/formatter.ts | 35 ++++++++---- 6 files changed, 87 insertions(+), 33 deletions(-) diff --git a/js/src/sdk/index.spec.ts b/js/src/sdk/index.spec.ts index 21309d73d0d..bcd80116007 100644 --- a/js/src/sdk/index.spec.ts +++ b/js/src/sdk/index.spec.ts @@ -26,54 +26,70 @@ describe("Basic SDK spec suite", () => { }); it("should handle 404 error gracefully", async () => { - const client = new Composio({ apiKey: COMPOSIO_API_KEY }); const mock = new AxiosMockAdapter(axiosClient.instance); - mock.onGet("/api/v1/apps").reply(404, { detail: "Not found" }); + const mockError = { + error: { + type: "NotFoundError", + name: "AppNotFoundError", + message: "Not found", + }, + }; + mock.onGet("/api/v1/apps").reply(404, mockError); + + const client = new Composio({ apiKey: COMPOSIO_API_KEY }); try { - await client.apps.list(); + const apps = await client.apps.list(); + console.log(apps); } catch (e) { - const error = e as ComposioError; - const errorCode = COMPOSIO_SDK_ERROR_CODES.BACKEND.NOT_FOUND; - const errorInfo = BASE_ERROR_CODE_INFO[errorCode]; - expect(error.errCode).toBe(errorCode); - expect(error.message).toContain(errorInfo.message); - expect(error.description).toBe(errorInfo.description); - expect(error.errorId).toBeDefined(); - expect(error.name).toBe("ComposioError"); - expect(error.possibleFix).toBe(errorInfo.possibleFix); + if (e instanceof ComposioError) { + console.log(e); + expect(e.errCode).toBe(COMPOSIO_SDK_ERROR_CODES.BACKEND.NOT_FOUND); + expect(e.description).toBe("Not found"); + expect(e.errorId).toBeDefined(); + expect(e.name).toBe("ComposioError"); + expect(e.possibleFix).toBe(e.possibleFix); + expect(e.message).toContain(mockError.error.message); + expect(e.message).toContain(mockError.error.name); + } else { + throw e; + } } mock.reset(); }); it("should handle 400 error gracefully", async () => { - const client = new Composio({ apiKey: COMPOSIO_API_KEY }); const mock = new AxiosMockAdapter(axiosClient.instance); mock .onGet("/api/v1/apps") - .reply(400, { errors: ["Invalid request for apps"] }); + .reply(400, { + error: { + type: "BadRequestError", + name: "InvalidRequestError", + message: "Invalid request for apps", + }, + }); + const client = new Composio({ apiKey: COMPOSIO_API_KEY }); try { await client.apps.list(); } catch (e) { const error = e as ComposioError; const errorCode = COMPOSIO_SDK_ERROR_CODES.BACKEND.BAD_REQUEST; expect(error.errCode).toBe(errorCode); - expect(error.message).toContain( - "Validation Errors while making request to https://backend.composio.dev/api/v1/apps" - ); + expect(error.message).toContain("InvalidRequestError "); + expect(error.message).toContain("InvalidRequestError"); expect(error.description).toContain("Invalid request for apps"); } mock.reset(); }); - it("should handle 500 and 502 error gracefully", async () => { - const client = new Composio({ apiKey: COMPOSIO_API_KEY }); + it("should handle 500 and 502 error gracefully, and without backend fix", async () => { const mock = new AxiosMockAdapter(axiosClient.instance); mock.onGet("/api/v1/apps").reply(500, { detail: "Internal Server Error" }); - + const client = new Composio({ apiKey: COMPOSIO_API_KEY }); try { await client.apps.list(); } catch (e) { @@ -105,6 +121,22 @@ describe("Basic SDK spec suite", () => { } mock.reset(); + + mock + .onGet("/api/v1/apps") + .reply(500, { + error: { + type: "NotFoundError", + name: "AppNotFoundError", + message: "Not found", + }, + }); + try { + await client.apps.list(); + } catch (e) { + const error = e as ComposioError; + expect(error.message).toContain("AppNotFoundError - NotFoundError"); + } }); it("should give request timeout error", async () => { diff --git a/js/src/sdk/index.ts b/js/src/sdk/index.ts index 7ac8a4a89ba..26c435e7027 100644 --- a/js/src/sdk/index.ts +++ b/js/src/sdk/index.ts @@ -22,6 +22,7 @@ import { ZGetExpectedParamsForUserParams, ZGetExpectedParamsRes, } from "../types/composio"; +import { getUUID } from "../utils/common"; import { ZAuthMode } from "./types/integration"; export type ComposioInputFieldsParams = z.infer< @@ -63,6 +64,7 @@ export class Composio { ); ComposioSDKContext.apiKey = apiKeyParsed; + ComposioSDKContext.sessionId = getUUID(); ComposioSDKContext.baseURL = baseURLParsed; ComposioSDKContext.frameworkRuntime = config?.runtime; diff --git a/js/src/sdk/models/backendClient.ts b/js/src/sdk/models/backendClient.ts index 7e0921d6744..83eb25761e3 100644 --- a/js/src/sdk/models/backendClient.ts +++ b/js/src/sdk/models/backendClient.ts @@ -1,3 +1,4 @@ +import { AxiosInstance } from "axios"; import apiClient from "../client/client"; import { client as axiosClient } from "../client/services.gen"; import { setAxiosClientConfig } from "../utils/config"; @@ -22,6 +23,7 @@ export class BackendClient { * The runtime environment where the client is being used. */ public runtime: string; + public instance: AxiosInstance; /** * Creates an instance of apiClientDetails. @@ -34,6 +36,7 @@ export class BackendClient { this.runtime = runtime || ""; this.apiKey = apiKey; this.baseUrl = baseUrl; + this.instance = axiosClient.instance; if (!apiKey) { throw CEG.getCustomError( @@ -86,5 +89,6 @@ export class BackendClient { }); setAxiosClientConfig(axiosClient.instance); + this.instance = axiosClient.instance; } } diff --git a/js/src/sdk/utils/composioContext.ts b/js/src/sdk/utils/composioContext.ts index c58068f018e..3ade6e3f440 100644 --- a/js/src/sdk/utils/composioContext.ts +++ b/js/src/sdk/utils/composioContext.ts @@ -11,6 +11,7 @@ class ComposioSDKContext { static frameworkRuntime?: string; static source?: string = "javascript"; static composioVersion?: string; + static sessionId?: string; } export default ComposioSDKContext; diff --git a/js/src/sdk/utils/error.ts b/js/src/sdk/utils/error.ts index cdfa9d8c849..372893bbad8 100644 --- a/js/src/sdk/utils/error.ts +++ b/js/src/sdk/utils/error.ts @@ -2,8 +2,8 @@ import { AxiosError } from "axios"; import { ZodError } from "zod"; import { ComposioError } from "./errors/src/composioError"; import { - BASE_ERROR_CODE_INFO, API_TO_SDK_ERROR_CODE, + BASE_ERROR_CODE_INFO, COMPOSIO_SDK_ERROR_CODES, } from "./errors/src/constants"; import { @@ -127,9 +127,9 @@ export class CEG { ? API_TO_SDK_ERROR_CODE[statusCode] || COMPOSIO_SDK_ERROR_CODES.BACKEND.UNKNOWN : COMPOSIO_SDK_ERROR_CODES.BACKEND.UNKNOWN; - + const errorDetails = getAPIErrorDetails( - error as AxiosError, + error as AxiosError ); const metadata = generateMetadataFromAxiosError(error); diff --git a/js/src/sdk/utils/errors/src/formatter.ts b/js/src/sdk/utils/errors/src/formatter.ts index 59dbe36adba..78ae6072d19 100644 --- a/js/src/sdk/utils/errors/src/formatter.ts +++ b/js/src/sdk/utils/errors/src/formatter.ts @@ -1,5 +1,9 @@ import { AxiosError } from "axios"; -import { BASE_ERROR_CODE_INFO, API_TO_SDK_ERROR_CODE, COMPOSIO_SDK_ERROR_CODES } from "./constants"; +import { + API_TO_SDK_ERROR_CODE, + BASE_ERROR_CODE_INFO, + COMPOSIO_SDK_ERROR_CODES, +} from "./constants"; export interface ErrorResponseData { error: { @@ -17,16 +21,22 @@ interface ErrorDetails { } export const getAPIErrorDetails = ( - axiosError: AxiosError, + axiosError: AxiosError ): ErrorDetails => { const statusCode = axiosError.response?.status; - const errorCode = statusCode ? API_TO_SDK_ERROR_CODE[statusCode] : COMPOSIO_SDK_ERROR_CODES.BACKEND.UNKNOWN; + const errorCode = statusCode + ? API_TO_SDK_ERROR_CODE[statusCode] + : COMPOSIO_SDK_ERROR_CODES.BACKEND.UNKNOWN; const predefinedError = BASE_ERROR_CODE_INFO[errorCode]; const defaultErrorDetails = { message: axiosError.message, - description: axiosError.response?.data?.error?.message || axiosError.response?.data?.error || axiosError.message, - possibleFix: "Please check the network connection, request parameters, and ensure the API endpoint is correct.", + description: + axiosError.response?.data?.error?.message || + axiosError.response?.data?.error || + axiosError.message, + possibleFix: + "Please check the network connection, request parameters, and ensure the API endpoint is correct.", }; const metadata = generateMetadataFromAxiosError(axiosError); @@ -34,7 +44,8 @@ export const getAPIErrorDetails = ( const errorNameFromBE = axiosError.response?.data?.error?.name; const errorTypeFromBE = axiosError.response?.data?.error?.type; const errorMessage = axiosError.response?.data?.error?.message; - const genericMessage = `${errorNameFromBE} ${errorTypeFromBE ? `- ${errorTypeFromBE}` : ""} on ${axiosError.config?.baseURL! + axiosError.config?.url!}`; + + const genericMessage = `${errorNameFromBE || predefinedError.message} ${errorTypeFromBE ? `- ${errorTypeFromBE}` : ""} on ${axiosError.config?.baseURL! + axiosError.config?.url!}`; switch (errorCode) { case COMPOSIO_SDK_ERROR_CODES.BACKEND.NOT_FOUND: @@ -46,16 +57,20 @@ export const getAPIErrorDetails = ( case COMPOSIO_SDK_ERROR_CODES.BACKEND.BAD_REQUEST: return { message: genericMessage, - description: errorMessage || predefinedError.description as string, - possibleFix: predefinedError.possibleFix! || defaultErrorDetails.possibleFix as string, + description: errorMessage || (predefinedError.description as string), + possibleFix: + predefinedError.possibleFix! || + (defaultErrorDetails.possibleFix as string), metadata, }; default: return { message: genericMessage, - description: errorMessage || predefinedError.description as string, - possibleFix: predefinedError.possibleFix! || defaultErrorDetails.possibleFix as string, + description: errorMessage || (predefinedError.description as string), + possibleFix: + predefinedError.possibleFix! || + (defaultErrorDetails.possibleFix as string), metadata, }; } From c19c9bfe9f6deed116a075788ecbbd6e30aaffe3 Mon Sep 17 00:00:00 2001 From: Himanshu Dixit Date: Thu, 26 Dec 2024 09:16:51 +0530 Subject: [PATCH 3/3] feat: change test --- js/src/sdk/base.toolset.spec.ts | 2 -- js/src/sdk/index.spec.ts | 34 ++++++++++++++------------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/js/src/sdk/base.toolset.spec.ts b/js/src/sdk/base.toolset.spec.ts index 82099e4f985..d48760523c1 100644 --- a/js/src/sdk/base.toolset.spec.ts +++ b/js/src/sdk/base.toolset.spec.ts @@ -42,7 +42,6 @@ describe("ComposioToolSet class tests", () => { actionName, toolSchema, }) => { - console.log("actionName", actionName); return { ...toolSchema, parameters: { @@ -56,7 +55,6 @@ describe("ComposioToolSet class tests", () => { const tools = await toolset.getToolsSchema({ actions: ["github_issues_create"], }); - console.log("tools", tools); }); it("should execute an action", async () => { diff --git a/js/src/sdk/index.spec.ts b/js/src/sdk/index.spec.ts index bcd80116007..f41db6b559b 100644 --- a/js/src/sdk/index.spec.ts +++ b/js/src/sdk/index.spec.ts @@ -40,10 +40,8 @@ describe("Basic SDK spec suite", () => { try { const apps = await client.apps.list(); - console.log(apps); } catch (e) { if (e instanceof ComposioError) { - console.log(e); expect(e.errCode).toBe(COMPOSIO_SDK_ERROR_CODES.BACKEND.NOT_FOUND); expect(e.description).toBe("Not found"); expect(e.errorId).toBeDefined(); @@ -61,15 +59,13 @@ describe("Basic SDK spec suite", () => { it("should handle 400 error gracefully", async () => { const mock = new AxiosMockAdapter(axiosClient.instance); - mock - .onGet("/api/v1/apps") - .reply(400, { - error: { - type: "BadRequestError", - name: "InvalidRequestError", - message: "Invalid request for apps", - }, - }); + mock.onGet("/api/v1/apps").reply(400, { + error: { + type: "BadRequestError", + name: "InvalidRequestError", + message: "Invalid request for apps", + }, + }); const client = new Composio({ apiKey: COMPOSIO_API_KEY }); try { @@ -122,15 +118,13 @@ describe("Basic SDK spec suite", () => { mock.reset(); - mock - .onGet("/api/v1/apps") - .reply(500, { - error: { - type: "NotFoundError", - name: "AppNotFoundError", - message: "Not found", - }, - }); + mock.onGet("/api/v1/apps").reply(500, { + error: { + type: "NotFoundError", + name: "AppNotFoundError", + message: "Not found", + }, + }); try { await client.apps.list(); } catch (e) {