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}