async_openai/
request_options.rs

1use reqwest::header::HeaderMap;
2use serde::Serialize;
3use url::Url;
4
5use crate::{config::OPENAI_API_BASE, error::OpenAIError};
6
7#[derive(Clone, Debug, Default)]
8pub struct RequestOptions {
9    query: Option<Vec<(String, String)>>,
10    headers: Option<HeaderMap>,
11    path: Option<String>,
12}
13
14impl RequestOptions {
15    pub(crate) fn new() -> Self {
16        Self {
17            query: None,
18            headers: None,
19            path: None,
20        }
21    }
22
23    pub(crate) fn with_path(&mut self, path: &str) -> Result<(), OpenAIError> {
24        if path.is_empty() {
25            return Err(OpenAIError::InvalidArgument(
26                "Path cannot be empty".to_string(),
27            ));
28        }
29        self.path = Some(path.to_string());
30        Ok(())
31    }
32
33    pub(crate) fn with_headers(&mut self, headers: HeaderMap) {
34        // merge with existing headers or update with new headers
35        if let Some(existing_headers) = &mut self.headers {
36            existing_headers.extend(headers);
37        } else {
38            self.headers = Some(headers);
39        }
40    }
41
42    pub(crate) fn with_header<K, V>(&mut self, key: K, value: V) -> Result<(), OpenAIError>
43    where
44        K: reqwest::header::IntoHeaderName,
45        V: TryInto<reqwest::header::HeaderValue>,
46        V::Error: Into<reqwest::header::InvalidHeaderValue>,
47    {
48        let value = value.try_into().map_err(|e| {
49            OpenAIError::InvalidArgument(format!("Invalid header value: {}", e.into()))
50        })?;
51        if let Some(headers) = &mut self.headers {
52            headers.insert(key, value);
53        } else {
54            let mut headers = HeaderMap::new();
55            headers.insert(key, value);
56            self.headers = Some(headers);
57        }
58        Ok(())
59    }
60
61    pub(crate) fn with_query<Q: Serialize + ?Sized>(
62        &mut self,
63        query: &Q,
64    ) -> Result<(), OpenAIError> {
65        // Use serde_urlencoded::Serializer directly to handle any serializable type
66        // similar to how reqwest does it. We create a temporary URL to use query_pairs_mut()
67        // which allows us to handle any serializable type, not just top-level maps/structs.
68        let mut url = Url::parse(OPENAI_API_BASE)
69            .map_err(|e| OpenAIError::InvalidArgument(format!("Failed to create URL: {}", e)))?;
70
71        {
72            let mut pairs = url.query_pairs_mut();
73            let serializer = serde_urlencoded::Serializer::new(&mut pairs);
74
75            query
76                .serialize(serializer)
77                .map_err(|e| OpenAIError::InvalidArgument(format!("Invalid query: {}", e)))?;
78        }
79
80        // Extract query pairs from the URL and append to our vec
81        let query = self.query.get_or_insert_with(Vec::new);
82        for (key, value) in url.query_pairs() {
83            query.push((key.to_string(), value.to_string()));
84        }
85
86        Ok(())
87    }
88
89    pub(crate) fn query(&self) -> &[(String, String)] {
90        self.query.as_deref().unwrap_or(&[])
91    }
92
93    pub(crate) fn headers(&self) -> Option<&HeaderMap> {
94        self.headers.as_ref()
95    }
96
97    pub(crate) fn path(&self) -> Option<&String> {
98        self.path.as_ref()
99    }
100}