diff --git a/jupiter-swap-api-client/src/lib.rs b/jupiter-swap-api-client/src/lib.rs index f4e55dd..da88672 100644 --- a/jupiter-swap-api-client/src/lib.rs +++ b/jupiter-swap-api-client/src/lib.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::sync::Arc; use quote::{InternalQuoteRequest, QuoteRequest, QuoteResponse}; use reqwest::{Client, Response}; @@ -12,84 +13,99 @@ pub mod serde_helpers; pub mod swap; pub mod transaction_config; -#[derive(Clone)] -pub struct JupiterSwapApiClient { - pub base_path: String, -} - #[derive(Debug, Error)] pub enum ClientError { + #[error("Network error: {0}")] + NetworkError(#[from] reqwest::Error), #[error("Request failed with status {status}: {body}")] RequestFailed { status: reqwest::StatusCode, body: String, }, #[error("Failed to deserialize response: {0}")] - DeserializationError(#[from] reqwest::Error), + DeserializationError(String), } -async fn check_is_success(response: Response) -> Result { - if !response.status().is_success() { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - return Err(ClientError::RequestFailed { status, body }); - } - Ok(response) -} - -async fn check_status_code_and_deserialize( - response: Response, -) -> Result { - let response = check_is_success(response).await?; - response - .json::() - .await - .map_err(ClientError::DeserializationError) +#[derive(Clone)] +pub struct JupiterSwapApiClient { + pub base_path: String, + // Optimization: Shared HTTP client for connection pooling + client: Client, } impl JupiterSwapApiClient { + /// Creates a new Jupiter API client with connection pooling. pub fn new(base_path: String) -> Self { - Self { base_path } + let client = Client::builder() + .pool_idle_timeout(std::time::Duration::from_secs(90)) + .build() + .unwrap_or_else(|_| Client::new()); + + Self { base_path, client } } + /// Internal helper to validate response and deserialize JSON. + async fn handle_response(&self, response: Response) -> Result { + let status = response.status(); + if !status.is_success() { + let body = response.text().await.unwrap_or_else(|_| "Could not read error body".to_string()); + return Err(ClientError::RequestFailed { status, body }); + } + + response + .json::() + .await + .map_err(|e| ClientError::DeserializationError(e.to_string())) + } + + /// Gets a quote for a swap. pub async fn quote(&self, quote_request: &QuoteRequest) -> Result { let url = format!("{}/quote", self.base_path); - let extra_args = quote_request.quote_args.clone(); + let extra_args = "e_request.quote_args; let internal_quote_request = InternalQuoteRequest::from(quote_request.clone()); - let response = Client::new() + + let response = self.client .get(url) .query(&internal_quote_request) - .query(&extra_args) + .query(extra_args) .send() .await?; - check_status_code_and_deserialize(response).await + + self.handle_response(response).await } + /// Performs a swap. pub async fn swap( &self, swap_request: &SwapRequest, - extra_args: Option>, + extra_args: Option<&HashMap>, ) -> Result { - let response = Client::new() - .post(format!("{}/swap", self.base_path)) + let url = format!("{}/swap", self.base_path); + + let response = self.client + .post(url) .query(&extra_args) .json(swap_request) .send() .await?; - check_status_code_and_deserialize(response).await + + self.handle_response(response).await } + /// Gets instructions for a swap without executing it. pub async fn swap_instructions( &self, swap_request: &SwapRequest, ) -> Result { - let response = Client::new() - .post(format!("{}/swap-instructions", self.base_path)) + let url = format!("{}/swap-instructions", self.base_path); + + let response = self.client + .post(url) .json(swap_request) .send() .await?; - check_status_code_and_deserialize::(response) - .await - .map(Into::into) + + let internal_res: SwapInstructionsResponseInternal = self.handle_response(response).await?; + Ok(internal_res.into()) } }