1use http::{HeaderMap, HeaderValue};
4use std::fmt::Debug;
5use url::Url;
6
7use crate::{HttpError, Result};
8
9#[non_exhaustive]
11#[derive(Clone, Debug, Default)]
12pub struct Config {
13 pub base_url: Option<Url>,
19 pub headers: HeaderMap,
21}
22
23impl Config {
24 #[must_use]
26 pub fn new() -> Self {
27 Self::default()
28 }
29}
30
31impl Config {
32 pub fn add_header(
39 mut self,
40 name: impl http::header::IntoHeaderName,
41 value: impl AsRef<str>,
42 ) -> Result<Self> {
43 let value =
44 HeaderValue::from_str(value.as_ref()).map_err(|e| HttpError::Io(e.to_string()))?;
45 self.headers.append(name, value);
46 Ok(self)
47 }
48
49 #[must_use]
51 pub fn set_base_url(mut self, base: Url) -> Self {
52 self.base_url = Some(base);
53 self
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn add_header_stores_valid_value() {
63 let config = Config::default().add_header("x-api-key", "secret").unwrap();
64 let val = config.headers.get("x-api-key").unwrap();
65 assert_eq!(val.to_str().unwrap(), "secret");
66 }
67
68 #[test]
69 fn add_header_called_twice_preserves_both_values() {
70 let config = Config::default()
71 .add_header("accept", "text/html")
72 .unwrap()
73 .add_header("accept", "application/json")
74 .unwrap();
75 let values: Vec<&str> = config
76 .headers
77 .get_all("accept")
78 .iter()
79 .map(|v| v.to_str().unwrap())
80 .collect();
81 assert_eq!(values, ["text/html", "application/json"]);
82 }
83
84 #[test]
85 fn add_header_rejects_invalid_value() {
86 let result = Config::default().add_header("x-bad", "val\x00ue");
88 assert!(
89 matches!(result, Err(HttpError::Io(_))),
90 "invalid header value must return HttpError::Io"
91 );
92 }
93}