use http::header::{HeaderName, HeaderValue};
use http::HeaderMap;
use serde::Serialize;
use crate::Error;
#[derive(Clone, Debug, Default)]
pub struct RequestOptions {
headers: HeaderMap,
query: Vec<(String, String)>,
}
impl RequestOptions {
pub fn new() -> Self {
Self::default()
}
pub fn header<K, V>(mut self, name: K, value: V) -> Result<Self, Error>
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
let name = HeaderName::try_from(name).map_err(|e| Error::builder(e.into()))?;
let value = HeaderValue::try_from(value).map_err(|e| Error::builder(e.into()))?;
self.headers.insert(name, value);
Ok(self)
}
pub fn headers(mut self, headers: HeaderMap) -> Self {
self.headers.extend(headers);
self
}
pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> Result<Self, Error> {
let encoded = serde_urlencoded::to_string(query).map_err(Error::builder)?;
self.query
.extend(url::form_urlencoded::parse(encoded.as_bytes()).into_owned());
Ok(self)
}
pub fn header_map(&self) -> &HeaderMap {
&self.headers
}
pub fn query_pairs(&self) -> &[(String, String)] {
&self.query
}
pub fn is_empty(&self) -> bool {
self.headers.is_empty() && self.query.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::header::{AUTHORIZATION, CONTENT_TYPE};
#[test]
fn header_replaces_and_headers_keeps_every_value() {
let mut extra = HeaderMap::new();
extra.append("x-tag", HeaderValue::from_static("a"));
extra.append("x-tag", HeaderValue::from_static("b"));
let options = RequestOptions::new()
.header("x-one", "1")
.unwrap()
.header("x-one", "2")
.unwrap()
.header(CONTENT_TYPE, HeaderValue::from_static("text/plain"))
.unwrap()
.headers(extra);
let headers = options.header_map();
assert_eq!(headers.get_all("x-one").iter().collect::<Vec<_>>(), ["2"]);
assert_eq!(
headers.get_all("x-tag").iter().collect::<Vec<_>>(),
["a", "b"]
);
assert_eq!(headers[CONTENT_TYPE], "text/plain");
}
#[test]
fn query_keeps_order_and_decodes() {
let options = RequestOptions::new()
.query(&[("a", "x y"), ("b", "&")])
.unwrap()
.query(&[("a", 2)])
.unwrap();
let pairs: Vec<(&str, &str)> = options
.query_pairs()
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();
assert_eq!(pairs, [("a", "x y"), ("b", "&"), ("a", "2")]);
}
#[test]
fn invalid_input_is_a_builder_error() {
assert!(RequestOptions::new()
.header("x bad", "v")
.unwrap_err()
.is_builder());
assert!(RequestOptions::new()
.header("x-bad", "line\nbreak")
.unwrap_err()
.is_builder());
assert!(RequestOptions::new()
.query(&[("a", [1, 2])])
.unwrap_err()
.is_builder());
}
#[test]
fn empty() {
assert!(RequestOptions::new().is_empty());
assert!(RequestOptions::default().is_empty());
assert!(!RequestOptions::new()
.header(AUTHORIZATION, "Bearer x")
.unwrap()
.is_empty());
assert!(!RequestOptions::new()
.query(&[("a", "")])
.unwrap()
.is_empty());
}
}