From cd5c76bfdb01276b29f3320904cce842815b0eb4 Mon Sep 17 00:00:00 2001 From: Himanshu Neema Date: Wed, 19 Nov 2025 11:50:55 -0800 Subject: [PATCH 1/2] refactor to avoid repetition --- async-openai/src/client.rs | 205 ++++++++++--------------------------- 1 file changed, 56 insertions(+), 149 deletions(-) diff --git a/async-openai/src/client.rs b/async-openai/src/client.rs index 7f0e0a4e..8ea0b0ee 100644 --- a/async-openai/src/client.rs +++ b/async-openai/src/client.rs @@ -197,6 +197,30 @@ impl Client { &self.config } + /// Helper function to build a request builder with common configuration + fn build_request_builder( + &self, + method: reqwest::Method, + path: &str, + request_options: &RequestOptions, + ) -> reqwest::RequestBuilder { + let mut request_builder = self + .http_client + .request(method, self.config.url(path)) + .query(&self.config.query()) + .headers(self.config.headers()); + + if let Some(headers) = request_options.headers() { + request_builder = request_builder.headers(headers.clone()); + } + + if !request_options.query().is_empty() { + request_builder = request_builder.query(request_options.query()); + } + + request_builder + } + /// Make a GET request to {path} and deserialize the response body pub(crate) async fn get( &self, @@ -207,21 +231,9 @@ impl Client { O: DeserializeOwned, { let request_maker = || async { - let mut request_builder = self - .http_client - .get(self.config.url(path)) - .query(&self.config.query()) - .headers(self.config.headers()); - - if let Some(headers) = request_options.headers() { - request_builder = request_builder.headers(headers.clone()); - } - - if !request_options.query().is_empty() { - request_builder = request_builder.query(request_options.query()); - } - - Ok(request_builder.build()?) + Ok(self + .build_request_builder(reqwest::Method::GET, path, request_options) + .build()?) }; self.execute(request_maker).await @@ -237,21 +249,9 @@ impl Client { O: DeserializeOwned, { let request_maker = || async { - let mut request_builder = self - .http_client - .delete(self.config.url(path)) - .query(&self.config.query()) - .headers(self.config.headers()); - - if let Some(headers) = request_options.headers() { - request_builder = request_builder.headers(headers.clone()); - } - - if !request_options.query().is_empty() { - request_builder = request_builder.query(request_options.query()); - } - - Ok(request_builder.build()?) + Ok(self + .build_request_builder(reqwest::Method::DELETE, path, request_options) + .build()?) }; self.execute(request_maker).await @@ -264,21 +264,9 @@ impl Client { request_options: &RequestOptions, ) -> Result<(Bytes, HeaderMap), OpenAIError> { let request_maker = || async { - let mut request_builder = self - .http_client - .get(self.config.url(path)) - .query(&self.config.query()) - .headers(self.config.headers()); - - if let Some(headers) = request_options.headers() { - request_builder = request_builder.headers(headers.clone()); - } - - if !request_options.query().is_empty() { - request_builder = request_builder.query(request_options.query()); - } - - Ok(request_builder.build()?) + Ok(self + .build_request_builder(reqwest::Method::GET, path, request_options) + .build()?) }; self.execute_raw(request_maker).await @@ -295,22 +283,10 @@ impl Client { I: Serialize, { let request_maker = || async { - let mut request_builder = self - .http_client - .post(self.config.url(path)) - .query(&self.config.query()) - .headers(self.config.headers()) - .json(&request); - - if let Some(headers) = request_options.headers() { - request_builder = request_builder.headers(headers.clone()); - } - - if !request_options.query().is_empty() { - request_builder = request_builder.query(request_options.query()); - } - - Ok(request_builder.build()?) + Ok(self + .build_request_builder(reqwest::Method::POST, path, request_options) + .json(&request) + .build()?) }; self.execute_raw(request_maker).await @@ -328,22 +304,10 @@ impl Client { O: DeserializeOwned, { let request_maker = || async { - let mut request_builder = self - .http_client - .post(self.config.url(path)) - .query(&self.config.query()) - .headers(self.config.headers()) - .json(&request); - - if let Some(headers) = request_options.headers() { - request_builder = request_builder.headers(headers.clone()); - } - - if !request_options.query().is_empty() { - request_builder = request_builder.query(request_options.query()); - } - - Ok(request_builder.build()?) + Ok(self + .build_request_builder(reqwest::Method::POST, path, request_options) + .json(&request) + .build()?) }; self.execute(request_maker).await @@ -361,22 +325,10 @@ impl Client { F: Clone, { let request_maker = || async { - let mut request_builder = self - .http_client - .post(self.config.url(path)) - .query(&self.config.query()) - .headers(self.config.headers()) - .multipart(
>::try_from(form.clone()).await?); - - if let Some(headers) = request_options.headers() { - request_builder = request_builder.headers(headers.clone()); - } - - if !request_options.query().is_empty() { - request_builder = request_builder.query(request_options.query()); - } - - Ok(request_builder.build()?) + Ok(self + .build_request_builder(reqwest::Method::POST, path, request_options) + .multipart(>::try_from(form.clone()).await?) + .build()?) }; self.execute_raw(request_maker).await @@ -395,22 +347,10 @@ impl Client { F: Clone, { let request_maker = || async { - let mut request_builder = self - .http_client - .post(self.config.url(path)) - .query(&self.config.query()) - .headers(self.config.headers()) - .multipart(>::try_from(form.clone()).await?); - - if let Some(headers) = request_options.headers() { - request_builder = request_builder.headers(headers.clone()); - } - - if !request_options.query().is_empty() { - request_builder = request_builder.query(request_options.query()); - } - - Ok(request_builder.build()?) + Ok(self + .build_request_builder(reqwest::Method::POST, path, request_options) + .multipart(>::try_from(form.clone()).await?) + .build()?) }; self.execute(request_maker).await @@ -429,20 +369,9 @@ impl Client { { // Build and execute request manually since multipart::Form is not Clone // and .eventsource() requires cloneability - let mut request_builder = self - .http_client - .post(self.config.url(path)) - .query(&self.config.query()) - .multipart(>::try_from(form.clone()).await?) - .headers(self.config.headers()); - - if let Some(headers) = request_options.headers() { - request_builder = request_builder.headers(headers.clone()); - } - - if !request_options.query().is_empty() { - request_builder = request_builder.query(request_options.query()); - } + let request_builder = self + .build_request_builder(reqwest::Method::POST, path, request_options) + .multipart(>::try_from(form.clone()).await?); let response = request_builder.send().await.map_err(OpenAIError::Reqwest)?; @@ -580,21 +509,10 @@ impl Client { I: Serialize, O: DeserializeOwned + std::marker::Send + 'static, { - let mut request_builder = self - .http_client - .post(self.config.url(path)) - .query(&self.config.query()) - .headers(self.config.headers()) + let request_builder = self + .build_request_builder(reqwest::Method::POST, path, request_options) .json(&request); - if let Some(headers) = request_options.headers() { - request_builder = request_builder.headers(headers.clone()); - } - - if !request_options.query().is_empty() { - request_builder = request_builder.query(request_options.query()); - } - let event_source = request_builder.eventsource().unwrap(); stream(event_source).await @@ -611,21 +529,10 @@ impl Client { I: Serialize, O: DeserializeOwned + std::marker::Send + 'static, { - let mut request_builder = self - .http_client - .post(self.config.url(path)) - .query(&self.config.query()) - .headers(self.config.headers()) + let request_builder = self + .build_request_builder(reqwest::Method::POST, path, request_options) .json(&request); - if let Some(headers) = request_options.headers() { - request_builder = request_builder.headers(headers.clone()); - } - - if !request_options.query().is_empty() { - request_builder = request_builder.query(request_options.query()); - } - let event_source = request_builder.eventsource().unwrap(); stream_mapped_raw_events(event_source, event_mapper).await From ad59163982483c098ca062caba631b3de7dc5858 Mon Sep 17 00:00:00 2001 From: Himanshu Neema Date: Wed, 19 Nov 2025 12:03:28 -0800 Subject: [PATCH 2/2] configurable per request path --- async-openai/src/client.rs | 11 ++++++++--- async-openai/src/request_options.rs | 16 ++++++++++++++++ async-openai/src/traits.rs | 6 ++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/async-openai/src/client.rs b/async-openai/src/client.rs index 8ea0b0ee..2c30cbc0 100644 --- a/async-openai/src/client.rs +++ b/async-openai/src/client.rs @@ -204,9 +204,14 @@ impl Client { path: &str, request_options: &RequestOptions, ) -> reqwest::RequestBuilder { - let mut request_builder = self - .http_client - .request(method, self.config.url(path)) + let mut request_builder = if let Some(path) = request_options.path() { + self.http_client + .request(method, self.config.url(path.as_str())) + } else { + self.http_client.request(method, self.config.url(path)) + }; + + request_builder = request_builder .query(&self.config.query()) .headers(self.config.headers()); diff --git a/async-openai/src/request_options.rs b/async-openai/src/request_options.rs index 91ce7669..04726772 100644 --- a/async-openai/src/request_options.rs +++ b/async-openai/src/request_options.rs @@ -8,6 +8,7 @@ use crate::{config::OPENAI_API_BASE, error::OpenAIError}; pub struct RequestOptions { query: Option>, headers: Option, + path: Option, } impl RequestOptions { @@ -15,9 +16,20 @@ impl RequestOptions { Self { query: None, headers: None, + path: None, } } + pub(crate) fn with_path(&mut self, path: &str) -> Result<(), OpenAIError> { + if path.is_empty() { + return Err(OpenAIError::InvalidArgument( + "Path cannot be empty".to_string(), + )); + } + self.path = Some(path.to_string()); + Ok(()) + } + pub(crate) fn with_headers(&mut self, headers: HeaderMap) { // merge with existing headers or update with new headers if let Some(existing_headers) = &mut self.headers { @@ -81,4 +93,8 @@ impl RequestOptions { pub(crate) fn headers(&self) -> Option<&HeaderMap> { self.headers.as_ref() } + + pub(crate) fn path(&self) -> Option<&String> { + self.path.as_ref() + } } diff --git a/async-openai/src/traits.rs b/async-openai/src/traits.rs index 65ad1441..d0489322 100644 --- a/async-openai/src/traits.rs +++ b/async-openai/src/traits.rs @@ -53,4 +53,10 @@ pub trait RequestOptionsBuilder: Sized { self.options_mut().with_query(query)?; Ok(self) } + + /// Add a path to RequestOptions + fn path>(mut self, path: P) -> Result { + self.options_mut().with_path(path.into().as_str())?; + Ok(self) + } }