1use std::time::Duration;
4
5use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
6
7#[derive(Debug, Clone, Default)]
23pub struct RequestOptions {
24 pub headers: Option<HeaderMap>,
26
27 pub query: Option<Vec<(String, String)>>,
29
30 pub extra_body: Option<serde_json::Value>,
32
33 pub timeout: Option<Duration>,
35}
36
37impl RequestOptions {
38 #[must_use]
40 pub fn new() -> Self {
41 Self::default()
42 }
43
44 #[must_use]
46 pub fn header(mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
47 let map = self.headers.get_or_insert_with(HeaderMap::new);
48 if let (Ok(n), Ok(v)) = (
49 name.as_ref().parse::<HeaderName>(),
50 value.as_ref().parse::<HeaderValue>(),
51 ) {
52 map.insert(n, v);
53 }
54 self
55 }
56
57 #[must_use]
59 pub fn headers(mut self, headers: HeaderMap) -> Self {
60 self.headers = Some(headers);
61 self
62 }
63
64 #[must_use]
66 pub fn query_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
67 self.query
68 .get_or_insert_with(Vec::new)
69 .push((key.into(), value.into()));
70 self
71 }
72
73 #[must_use]
75 pub fn query(mut self, params: Vec<(String, String)>) -> Self {
76 self.query = Some(params);
77 self
78 }
79
80 #[must_use]
82 pub fn extra_body(mut self, value: serde_json::Value) -> Self {
83 self.extra_body = Some(value);
84 self
85 }
86
87 #[must_use]
89 pub fn timeout(mut self, duration: Duration) -> Self {
90 self.timeout = Some(duration);
91 self
92 }
93
94 #[must_use]
96 pub fn merge(&self, other: &RequestOptions) -> RequestOptions {
97 RequestOptions {
98 headers: merge_headers(&self.headers, &other.headers),
99 query: merge_query(&self.query, &other.query),
100 extra_body: merge_json(&self.extra_body, &other.extra_body),
101 timeout: other.timeout.or(self.timeout),
102 }
103 }
104
105 pub fn is_empty(&self) -> bool {
107 self.headers.is_none()
108 && self.query.is_none()
109 && self.extra_body.is_none()
110 && self.timeout.is_none()
111 }
112}
113
114fn merge_headers(a: &Option<HeaderMap>, b: &Option<HeaderMap>) -> Option<HeaderMap> {
116 match (a, b) {
117 (None, None) => None,
118 (Some(a), None) => Some(a.clone()),
119 (None, Some(b)) => Some(b.clone()),
120 (Some(a), Some(b)) => {
121 let mut merged = a.clone();
122 for (key, value) in b.iter() {
123 merged.insert(key.clone(), value.clone());
124 }
125 Some(merged)
126 }
127 }
128}
129
130fn merge_query(
132 a: &Option<Vec<(String, String)>>,
133 b: &Option<Vec<(String, String)>>,
134) -> Option<Vec<(String, String)>> {
135 match (a, b) {
136 (None, None) => None,
137 (Some(a), None) => Some(a.clone()),
138 (None, Some(b)) => Some(b.clone()),
139 (Some(a), Some(b)) => {
140 let mut merged = a.clone();
141 merged.extend(b.iter().cloned());
142 Some(merged)
143 }
144 }
145}
146
147fn merge_json(
149 a: &Option<serde_json::Value>,
150 b: &Option<serde_json::Value>,
151) -> Option<serde_json::Value> {
152 match (a, b) {
153 (None, None) => None,
154 (Some(a), None) => Some(a.clone()),
155 (None, Some(b)) => Some(b.clone()),
156 (Some(a), Some(b)) => Some(deep_merge_value(a.clone(), b.clone())),
157 }
158}
159
160fn deep_merge_value(a: serde_json::Value, b: serde_json::Value) -> serde_json::Value {
162 use serde_json::Value;
163 match (a, b) {
164 (Value::Object(mut a_map), Value::Object(b_map)) => {
165 for (k, v) in b_map {
166 let merged = match a_map.remove(&k) {
167 Some(existing) => deep_merge_value(existing, v),
168 None => v,
169 };
170 a_map.insert(k, merged);
171 }
172 Value::Object(a_map)
173 }
174 (_, b) => b,
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_new_is_empty() {
185 let opts = RequestOptions::new();
186 assert!(opts.is_empty());
187 }
188
189 #[test]
190 fn test_header_builder() {
191 let opts = RequestOptions::new()
192 .header("X-Custom", "value1")
193 .header("X-Other", "value2");
194 let headers = opts.headers.unwrap();
195 assert_eq!(headers.get("X-Custom").unwrap(), "value1");
196 assert_eq!(headers.get("X-Other").unwrap(), "value2");
197 }
198
199 #[test]
200 fn test_query_param_builder() {
201 let opts = RequestOptions::new()
202 .query_param("foo", "bar")
203 .query_param("baz", "qux");
204 let query = opts.query.unwrap();
205 assert_eq!(query.len(), 2);
206 assert_eq!(query[0], ("foo".to_string(), "bar".to_string()));
207 }
208
209 #[test]
210 fn test_timeout_builder() {
211 let opts = RequestOptions::new().timeout(Duration::from_secs(30));
212 assert_eq!(opts.timeout, Some(Duration::from_secs(30)));
213 }
214
215 #[test]
216 fn test_extra_body_builder() {
217 let opts = RequestOptions::new().extra_body(serde_json::json!({"key": "val"}));
218 assert_eq!(opts.extra_body.unwrap(), serde_json::json!({"key": "val"}));
219 }
220
221 #[test]
222 fn test_merge_empty() {
223 let a = RequestOptions::new();
224 let b = RequestOptions::new();
225 let merged = a.merge(&b);
226 assert!(merged.is_empty());
227 }
228
229 #[test]
230 fn test_merge_headers_b_wins() {
231 let a = RequestOptions::new().header("X-A", "1").header("X-B", "2");
232 let b = RequestOptions::new().header("X-A", "overridden");
233 let merged = a.merge(&b);
234 let headers = merged.headers.unwrap();
235 assert_eq!(headers.get("X-A").unwrap(), "overridden");
236 assert_eq!(headers.get("X-B").unwrap(), "2");
237 }
238
239 #[test]
240 fn test_merge_query_appends() {
241 let a = RequestOptions::new().query_param("a", "1");
242 let b = RequestOptions::new().query_param("b", "2");
243 let merged = a.merge(&b);
244 let query = merged.query.unwrap();
245 assert_eq!(query.len(), 2);
246 }
247
248 #[test]
249 fn test_merge_timeout_b_wins() {
250 let a = RequestOptions::new().timeout(Duration::from_secs(10));
251 let b = RequestOptions::new().timeout(Duration::from_secs(30));
252 let merged = a.merge(&b);
253 assert_eq!(merged.timeout, Some(Duration::from_secs(30)));
254 }
255
256 #[test]
257 fn test_merge_timeout_a_kept_when_b_none() {
258 let a = RequestOptions::new().timeout(Duration::from_secs(10));
259 let b = RequestOptions::new();
260 let merged = a.merge(&b);
261 assert_eq!(merged.timeout, Some(Duration::from_secs(10)));
262 }
263
264 #[test]
265 fn test_deep_merge_json() {
266 let a = serde_json::json!({"x": 1, "nested": {"a": 1, "b": 2}});
267 let b = serde_json::json!({"y": 2, "nested": {"b": 99, "c": 3}});
268 let merged = deep_merge_value(a, b);
269 assert_eq!(merged["x"], 1);
270 assert_eq!(merged["y"], 2);
271 assert_eq!(merged["nested"]["a"], 1);
272 assert_eq!(merged["nested"]["b"], 99);
273 assert_eq!(merged["nested"]["c"], 3);
274 }
275}