Skip to main content

outfox_openai/
request_options.rs

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