use crate::json::{self, JsonValue};
use std::collections::HashMap;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Response {
pub status: u16,
pub headers: Vec<(String, String)>,
body: Vec<u8>,
header_index: HashMap<String, Vec<usize>>,
}
impl Response {
#[must_use]
pub fn new(status: u16, headers: Vec<(String, String)>, body: Vec<u8>) -> Self {
let mut header_index: HashMap<String, Vec<usize>> = HashMap::new();
for (i, (k, _)) in headers.iter().enumerate() {
header_index.entry(k.to_lowercase()).or_default().push(i);
}
Self {
status,
headers,
body,
header_index,
}
}
#[inline]
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.body
}
#[inline]
#[must_use]
pub fn text(&self) -> Option<&str> {
std::str::from_utf8(&self.body).ok()
}
#[must_use]
pub const fn is_success(&self) -> bool {
self.status >= 200 && self.status < 300
}
#[must_use]
pub const fn is_client_error(&self) -> bool {
self.status >= 400 && self.status < 500
}
#[must_use]
pub const fn is_server_error(&self) -> bool {
self.status >= 500 && self.status < 600
}
#[must_use]
pub const fn status(&self) -> u16 {
self.status
}
#[inline]
#[must_use]
pub fn body(self) -> Vec<u8> {
self.body
}
#[inline]
#[must_use]
pub fn headers(&self) -> &[(String, String)] {
&self.headers
}
#[inline]
#[must_use]
pub fn header(&self, name: &str) -> Option<&str> {
let indices = if name.bytes().all(|b| !b.is_ascii_uppercase()) {
self.header_index.get(name)
} else {
self.header_index.get(&name.to_lowercase())
};
indices
.and_then(|idx| idx.first())
.and_then(|&i| self.headers.get(i))
.map(|(_, v)| v.as_str())
}
#[must_use]
pub fn header_all(&self, name: &str) -> Vec<&str> {
let indices = if name.bytes().all(|b| !b.is_ascii_uppercase()) {
self.header_index.get(name)
} else {
self.header_index.get(&name.to_lowercase())
};
indices
.map(|idx| {
idx.iter()
.filter_map(|&i| self.headers.get(i).map(|(_, v)| v.as_str()))
.collect()
})
.unwrap_or_default()
}
#[must_use]
pub fn json_with<T>(&self, parse: impl FnOnce(&[u8]) -> Option<T>) -> Option<T> {
if self.body.is_empty() {
None
} else {
parse(&self.body)
}
}
#[must_use]
pub fn json(&self) -> Option<JsonValue> {
self.json_with(json::try_parse)
}
}