use alux_http::{HttpMethod, HttpStatus};
use alux_http_direct::{DirectBody, DirectError, DirectRequest, DirectResponse};
use bytes::Bytes;
use core::fmt::Display;
use core::pin::pin;
use futures::TryStreamExt;
use http_body_util::combinators::UnsyncBoxBody;
use http_body_util::{BodyExt, Full, StreamBody};
use hyper::body::{Body, Frame};
use hyper::header::{HeaderName, HeaderValue};
use hyper::{Request, Response, StatusCode};
use std::io::Error as IoError;
pub type HyperBody = UnsyncBoxBody<Bytes, IoError>;
pub type HyperAnswer = Response<HyperBody>;
pub(crate) const BODY_LIMIT: usize = 8 * 1024 * 1024;
const TOO_LARGE: HttpStatus = HttpStatus::new(413);
pub(crate) async fn asked<Sent>(request: Request<Sent>, reading: usize) -> Result<DirectRequest, DirectResponse>
where
Sent: Body<Data = Bytes>,
Sent::Error: Display,
{
let (head, sent) = request.into_parts();
let Some(method) = named(head.method.as_str()) else {
return Err(DirectError::method_not_allowed(head.uri.path()).into());
};
let mut asked = DirectRequest::new(method, head.uri.path());
if let Some(query) = head.uri.query() {
asked = asked.with_query(query);
}
for (name, value) in &head.headers {
if let Ok(value) = value.to_str() {
asked = asked.with_header(name.as_str(), value);
}
}
Ok(asked.with_body(read(sent, reading).await?))
}
async fn read<Sent>(sent: Sent, reading: usize) -> Result<Vec<u8>, DirectResponse>
where
Sent: Body<Data = Bytes>,
Sent::Error: Display,
{
let mut body = Vec::new();
let mut sent = pin!(sent);
while let Some(frame) = sent.frame().await {
let frame = frame.map_err(|error| DirectResponse::from(DirectError::unreadable("body", &error.to_string())))?;
let Ok(data) = frame.into_data() else {
continue;
};
if body.len() + data.len() > reading {
let refused = format!("the body is larger than the {reading} bytes this service reads");
return Err(DirectError::new(TOO_LARGE, refused).into());
}
body.extend_from_slice(&data);
}
Ok(body)
}
fn named(method: &str) -> Option<HttpMethod> {
HttpMethod::ALL.iter().copied().find(|stated| stated.label() == method)
}
pub(crate) fn answered(answer: DirectResponse) -> HyperAnswer {
let status = StatusCode::from_u16(answer.status().code()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
let stated = answer.headers().map(|(name, value)| (name.to_owned(), value.to_owned())).collect::<Vec<_>>();
let mut response = Response::new(carried(answer.into_body()));
*response.status_mut() = status;
for (name, value) in stated {
if let (Ok(name), Ok(value)) = (HeaderName::from_bytes(name.as_bytes()), HeaderValue::from_str(&value)) {
response.headers_mut().append(name, value);
}
}
response
}
fn carried(body: DirectBody) -> HyperBody {
match body {
DirectBody::Stated(body) => Full::new(Bytes::from(body)).map_err(|never| match never {}).boxed_unsync(),
DirectBody::Produced(chunks) => {
StreamBody::new(chunks.map_ok(|chunk| Frame::data(Bytes::from(chunk)))).boxed_unsync()
}
}
}