pub struct Response {
pub status: u16,
headers: Vec<(String, String)>,
body: Vec<u8>,
}
impl Response {
pub(crate) fn new(status: u16, headers: Vec<(String, String)>, body: Vec<u8>) -> Self {
Self {
status,
headers,
body,
}
}
#[must_use]
pub fn header(&self, name: &str) -> Option<String> {
self.headers
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case(name))
.map(|(_, value)| value.clone())
}
#[must_use]
pub fn location(&self) -> Option<String> {
self.header("location")
}
#[must_use]
pub fn set_cookies(&self) -> Vec<String> {
self.headers
.iter()
.filter(|(key, _)| key.eq_ignore_ascii_case("set-cookie"))
.map(|(_, value)| value.clone())
.collect()
}
#[must_use]
pub fn set_cookie_value(&self, name: &str) -> Option<String> {
let prefix = format!("{name}=");
self.set_cookies()
.into_iter()
.find(|cookie| cookie.starts_with(&prefix))
.and_then(|cookie| {
cookie
.split(';')
.next()
.and_then(|pair| pair.split_once('='))
.map(|(_, value)| value.to_owned())
})
}
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.body
}
#[must_use]
pub fn text(&self) -> String {
String::from_utf8_lossy(&self.body).into_owned()
}
#[cfg(feature = "json")]
pub fn json<T: serde::de::DeserializeOwned>(&self) -> crate::error::Result<T> {
Ok(serde_json::from_slice(&self.body)?)
}
}
#[cfg(test)]
mod tests {
use super::Response;
fn response(headers: &[(&str, &str)]) -> Response {
Response::new(
200,
headers
.iter()
.map(|(k, v)| ((*k).to_owned(), (*v).to_owned()))
.collect(),
Vec::new(),
)
}
#[test]
fn collects_every_set_cookie_entry() {
let response = response(&[
("content-type", "text/html"),
("set-cookie", "a=1; Path=/"),
("set-cookie", "b=2; HttpOnly"),
]);
assert_eq!(response.set_cookies(), vec!["a=1; Path=/", "b=2; HttpOnly"]);
}
#[test]
fn cookie_value_stops_at_attributes() {
let response = response(&[("set-cookie", "lemonldap=abc; Path=/; HttpOnly")]);
assert_eq!(
response.set_cookie_value("lemonldap").as_deref(),
Some("abc")
);
}
#[test]
fn cookie_value_keeps_embedded_equals() {
let response = response(&[("set-cookie", "token=a=b; Secure")]);
assert_eq!(response.set_cookie_value("token").as_deref(), Some("a=b"));
}
#[test]
fn missing_cookie_is_none() {
let response = response(&[("set-cookie", "other=1")]);
assert_eq!(response.set_cookie_value("lemonldap"), None);
}
#[test]
fn header_lookup_is_case_insensitive() {
let response = response(&[("location", "https://example.org/next")]);
assert_eq!(
response.header("Location").as_deref(),
Some("https://example.org/next")
);
assert_eq!(
response.location().as_deref(),
Some("https://example.org/next")
);
}
}