toolcraft_request/
header_map.rs

1use crate::error::{Error, Result};
2
3/// Wrapper for HTTP headers used in request construction.
4#[derive(Debug, Clone)]
5pub struct HeaderMap {
6    headers: reqwest::header::HeaderMap,
7}
8
9impl HeaderMap {
10    /// Create a new empty HeaderMap.
11    pub fn new() -> Self {
12        HeaderMap {
13            headers: reqwest::header::HeaderMap::new(),
14        }
15    }
16
17    /// Create HeaderMap with default headers for JSON requests.
18    ///
19    /// Sets:
20    /// - `Content-Type: application/json`
21    /// - `Accept: application/json`
22    ///
23    /// # Example
24    /// ```
25    /// use toolcraft_request::{Request, HeaderMap};
26    /// use serde_json::json;
27    ///
28    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
29    /// let mut client = Request::new()?;
30    ///
31    /// // Use preset JSON headers
32    /// let headers = HeaderMap::for_json()?;
33    /// client.set_default_headers(headers);
34    ///
35    /// let body = json!({"key": "value"});
36    /// let response = client.post("/api", &body, None).await?;
37    /// # Ok(())
38    /// # }
39    /// ```
40    pub fn for_json() -> Result<Self> {
41        let mut headers = Self::new();
42        headers.insert("Content-Type", "application/json".to_string())?;
43        headers.insert("Accept", "application/json".to_string())?;
44        Ok(headers)
45    }
46
47    /// Create HeaderMap for form-data requests.
48    ///
49    /// Returns an empty HeaderMap because:
50    /// - `Content-Type` is automatically set by `post_form()` with the correct boundary
51    /// - Manual setting would break multipart/form-data encoding
52    ///
53    /// # Example
54    /// ```
55    /// use toolcraft_request::{FormField, HeaderMap, Request};
56    ///
57    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
58    /// let mut client = Request::new()?;
59    ///
60    /// // For form uploads, use empty headers or add custom ones
61    /// let headers = HeaderMap::for_form();
62    /// client.set_default_headers(headers);
63    ///
64    /// let fields = vec![FormField::text("name", "value")];
65    /// let response = client.post_form("/upload", fields, None).await?;
66    /// # Ok(())
67    /// # }
68    /// ```
69    pub fn for_form() -> Self {
70        Self::new()
71    }
72
73    /// Insert a header key-value pair.
74    /// If the key already exists, the old value is replaced.
75    ///
76    /// # Example
77    /// ```
78    /// use toolcraft_request::HeaderMap;
79    ///
80    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
81    /// let mut headers = HeaderMap::new();
82    /// headers.insert("Authorization", "Bearer token".to_string())?;
83    ///
84    /// // Dynamic header names are supported
85    /// let header_name = "X-Custom-Header".to_string();
86    /// headers.insert(header_name, "value".to_string())?;
87    /// # Ok(())
88    /// # }
89    /// ```
90    pub fn insert(&mut self, key: impl AsRef<str>, value: String) -> Result<()> {
91        let header_name = reqwest::header::HeaderName::from_bytes(key.as_ref().as_bytes())
92            .map_err(|_| Error::ErrorMessage("invalid header name".into()))?;
93        let header_value = reqwest::header::HeaderValue::from_str(&value)
94            .map_err(|_| Error::ErrorMessage("invalid header value".into()))?;
95        self.headers.insert(header_name, header_value);
96        Ok(())
97    }
98
99    /// Get the value of a header as String.
100    ///
101    /// # Example
102    /// ```
103    /// use toolcraft_request::HeaderMap;
104    ///
105    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
106    /// let mut headers = HeaderMap::new();
107    /// headers.insert("Content-Type", "application/json".to_string())?;
108    ///
109    /// assert_eq!(
110    ///     headers.get("Content-Type"),
111    ///     Some("application/json".to_string())
112    /// );
113    /// assert_eq!(headers.get("Missing"), None);
114    /// # Ok(())
115    /// # }
116    /// ```
117    pub fn get(&self, key: impl AsRef<str>) -> Option<String> {
118        let header_name = reqwest::header::HeaderName::from_bytes(key.as_ref().as_bytes()).ok()?;
119        self.headers
120            .get(&header_name)
121            .map(|v| v.to_str().unwrap_or_default().to_string())
122    }
123
124    /// Get reference to the internal reqwest HeaderMap.
125    pub fn inner(&self) -> &reqwest::header::HeaderMap {
126        &self.headers
127    }
128
129    /// Get mutable reference to the internal reqwest HeaderMap.
130    pub fn inner_mut(&mut self) -> &mut reqwest::header::HeaderMap {
131        &mut self.headers
132    }
133
134    /// Remove a header by key and return its value if it existed.
135    ///
136    /// # Example
137    /// ```
138    /// use toolcraft_request::HeaderMap;
139    ///
140    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
141    /// let mut headers = HeaderMap::new();
142    /// headers.insert("Content-Type", "application/json".to_string())?;
143    ///
144    /// let removed = headers.remove("Content-Type");
145    /// assert_eq!(removed, Some("application/json".to_string()));
146    /// assert_eq!(headers.get("Content-Type"), None);
147    /// # Ok(())
148    /// # }
149    /// ```
150    pub fn remove(&mut self, key: impl AsRef<str>) -> Option<String> {
151        let header_name = reqwest::header::HeaderName::from_bytes(key.as_ref().as_bytes()).ok()?;
152        self.headers
153            .remove(&header_name)
154            .map(|v| v.to_str().unwrap_or_default().to_string())
155    }
156
157    /// Check if a header exists.
158    ///
159    /// # Example
160    /// ```
161    /// use toolcraft_request::HeaderMap;
162    ///
163    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
164    /// let mut headers = HeaderMap::new();
165    /// headers.insert("Authorization", "Bearer token".to_string())?;
166    ///
167    /// assert!(headers.contains("Authorization"));
168    /// assert!(!headers.contains("Missing"));
169    /// # Ok(())
170    /// # }
171    /// ```
172    pub fn contains(&self, key: impl AsRef<str>) -> bool {
173        if let Ok(header_name) = reqwest::header::HeaderName::from_bytes(key.as_ref().as_bytes()) {
174            self.headers.contains_key(&header_name)
175        } else {
176            false
177        }
178    }
179
180    /// Merge another HeaderMap into this one.
181    /// If a key exists in both, the value from `other` will overwrite.
182    ///
183    /// # Example
184    /// ```
185    /// use toolcraft_request::HeaderMap;
186    ///
187    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
188    /// let mut default_headers = HeaderMap::new();
189    /// default_headers.insert("User-Agent", "MyApp/1.0".to_string())?;
190    ///
191    /// let mut custom_headers = HeaderMap::new();
192    /// custom_headers.insert("Authorization", "Bearer token".to_string())?;
193    ///
194    /// default_headers.merge(custom_headers);
195    /// # Ok(())
196    /// # }
197    /// ```
198    pub fn merge(&mut self, other: HeaderMap) {
199        self.headers.extend(other.headers);
200    }
201}
202
203impl Default for HeaderMap {
204    fn default() -> Self {
205        Self::new()
206    }
207}