macro_rules! response {
($name:ident, $status:expr) => {
pub fn $name<S>(content: S) -> Self
where S: Into<String> + AsRef<str> {
Self::new($status, content.into())
}
};
}
#[derive(Clone)]
#[non_exhaustive]
pub struct Response {
status: i32,
payload: Payload,
mime: Option<String>,
character_set: Option<String>,
languages: Option<Vec<String>>,
}
#[derive(Clone)]
enum Payload {
Text(String),
Binary(Vec<u8>),
}
impl Response {
response!(input, 10);
response!(sensitive_input, 11);
response!(temporary_redirect, 30);
response!(permanent_redirect, 31);
response!(temporary_failure, 40);
response!(server_unavailable, 41);
response!(cgi_error, 42);
response!(proxy_error, 43);
response!(slow_down, 44);
response!(permanent_failure, 50);
response!(not_found, 51);
response!(gone, 52);
response!(proxy_refused, 53);
response!(bad_request, 59);
response!(client_certificate_required, 60);
response!(certificate_not_authorised, 61);
response!(certificate_not_valid, 62);
#[allow(clippy::needless_pass_by_value)]
pub fn success(content: impl ToString) -> Self {
Self::new(20, content.to_string())
}
#[must_use]
pub fn binary_success(
content: impl AsRef<[u8]>,
mime: impl Into<String> + AsRef<str>,
) -> Self {
let mut response = Self::new(21, String::new());
response.payload = Payload::Binary(content.as_ref().to_vec());
response.with_mime(mime);
response
}
#[cfg(feature = "auto-deduce-mime")]
#[must_use]
pub fn binary_success_auto(content: &[u8]) -> Self {
let mut response = Self::new(22, String::new());
response.with_mime(tree_magic_mini::from_u8(content));
response.payload = Payload::Binary(content.to_vec());
response
}
#[must_use]
pub fn new(status: i32, content: impl Into<String> + AsRef<str>) -> Self {
Self {
status,
mime: None,
payload: if matches!(status, 21 | 22) {
Payload::Binary(Vec::new())
} else {
Payload::Text(content.into())
},
character_set: None,
languages: None,
}
}
#[doc(hidden)]
#[must_use]
pub fn serialize_body(self, header: &str, footer: &str) -> Vec<u8> {
if let Payload::Binary(bytes) = self.payload {
return bytes;
}
let mut body = Vec::with_capacity(self.body_capacity(header, footer));
self.append_body(&mut body, header, footer);
body
}
pub(crate) const fn body_capacity(
&self,
header: &str,
footer: &str,
) -> usize {
match &self.payload {
Payload::Text(content) if self.status == 20 =>
header.len() + content.len() + footer.len() + 2,
Payload::Binary(bytes) => bytes.len(),
Payload::Text(_) => 0,
}
}
pub(crate) fn append_body(
&self,
body: &mut Vec<u8>,
header: &str,
footer: &str,
) {
match &self.payload {
Payload::Text(content) if self.status == 20 => {
let text = self.mime.as_deref().is_none_or(|mime| {
mime
.split_once('/')
.is_some_and(|(kind, _)| kind.eq_ignore_ascii_case("text"))
});
if text {
append_text(body, header);
append_text(body, content);
} else {
body.extend_from_slice(header.as_bytes());
body.extend_from_slice(content.as_bytes());
}
body.push(b'\n');
if text {
append_text(body, footer);
if body.last() != Some(&b'\n') {
body.push(b'\n');
}
} else {
body.extend_from_slice(footer.as_bytes());
}
}
Payload::Binary(bytes) => body.extend_from_slice(bytes),
Payload::Text(_) => {}
}
}
#[must_use]
pub const fn status(&self) -> i32 { self.status }
#[must_use]
pub fn content(&self) -> Option<&str> {
match &self.payload {
Payload::Text(content) => Some(content),
Payload::Binary(_) => None,
}
}
pub const fn content_mut(&mut self) -> Option<&mut String> {
match &mut self.payload {
Payload::Text(content) => Some(content),
Payload::Binary(_) => None,
}
}
#[must_use]
pub fn binary_content(&self) -> Option<&[u8]> {
match &self.payload {
Payload::Binary(bytes) => Some(bytes),
Payload::Text(_) => None,
}
}
pub const fn binary_content_mut(&mut self) -> Option<&mut Vec<u8>> {
match &mut self.payload {
Payload::Binary(bytes) => Some(bytes),
Payload::Text(_) => None,
}
}
#[must_use]
pub fn mime(&self) -> Option<&str> { self.mime.as_deref() }
pub const fn mime_mut(&mut self) -> &mut Option<String> { &mut self.mime }
#[must_use]
pub fn character_set(&self) -> Option<&str> { self.character_set.as_deref() }
pub const fn character_set_mut(&mut self) -> &mut Option<String> {
&mut self.character_set
}
#[must_use]
pub fn languages(&self) -> Option<&[String]> { self.languages.as_deref() }
pub const fn languages_mut(&mut self) -> &mut Option<Vec<String>> {
&mut self.languages
}
pub fn with_mime(
&mut self,
mime: impl Into<String> + AsRef<str>,
) -> &mut Self {
self.mime = Some(mime.into());
self
}
pub fn with_character_set(
&mut self,
character_set: impl Into<String> + AsRef<str>,
) -> &mut Self {
self.character_set = Some(character_set.into());
self
}
pub fn with_languages<S>(&mut self, languages: impl AsRef<[S]>) -> &mut Self
where S: Into<String> + AsRef<str> {
self.languages = Some(
languages
.as_ref()
.iter()
.map(|s| s.as_ref().to_string())
.collect::<Vec<String>>(),
);
self
}
}
fn append_text(body: &mut Vec<u8>, mut text: &str) {
while let Some(position) = text.find('\r') {
body.extend_from_slice(&text.as_bytes()[..position]);
body.push(b'\n');
text = &text[position + 1..];
text = text.strip_prefix('\n').unwrap_or(text);
}
body.extend_from_slice(text.as_bytes());
}
impl std::future::IntoFuture for Response {
type IntoFuture = std::future::Ready<Self::Output>;
type Output = Self;
fn into_future(self) -> Self::IntoFuture { std::future::ready(self) }
}