use std::convert::Infallible;
use std::future::Future;
use axum::http::Request as AxumRequest;
use axum::response::{IntoResponse, Response as AxumResponse};
use axum::{self, Extension};
use std::net::SocketAddr;
use axum::body::{Body, BoxBody, Bytes};
use axum::handler::Handler;
use axum::routing::{any_service, on, IntoMakeService, MethodFilter, MethodRouter, Route};
use hyper::server::conn::AddrIncoming;
use tower_service::Service;
pub use axum::http::StatusCode;
use crate::context::PuffContext;
use crate::types::text::Text;
pub use axum::response::Json;
pub type Request = AxumRequest<Body>;
pub type Response = AxumResponse<BoxBody>;
pub type ResponseBuilder = AxumResponse<()>;
#[derive(Clone)]
pub struct Router<S = ()>(axum::Router<S>);
pub trait PuffHandler<Inp, S, Res> {
fn into_handler(self, filter: MethodFilter) -> MethodRouter<S>;
}
pub struct AxumHandlerArgs<T>(T);
impl<F, S, T1>
PuffHandler<AxumHandlerArgs<T1>, S, <<F as Handler<T1, S>>::Future as Future>::Output> for F
where
S: Send + Sync + 'static,
T1: Send + Sync + 'static,
F: Handler<T1, S>,
{
fn into_handler(self, filter: MethodFilter) -> MethodRouter<S> {
on(filter, self)
}
}
impl<S> Router<S>
where
S: Send + Sync + Default + 'static,
{
pub fn new() -> Self {
Self(axum::Router::default())
}
pub fn on<TextLike, H, T1, Res>(self, filter: MethodFilter, path: TextLike, handler: H) -> Self
where
TextLike: Into<Text>,
Res: IntoResponse,
H: PuffHandler<T1, S, Res>,
{
Self(self.0.route(
&path.into(),
handler.into_handler(filter), ))
}
pub fn get<TextLike, H, T1, Res>(self, path: TextLike, handler: H) -> Self
where
TextLike: Into<Text>,
Res: IntoResponse,
H: PuffHandler<T1, S, Res>,
{
self.on(MethodFilter::GET, path, handler)
}
pub fn post<TextLike, H, T1, Res>(self, path: TextLike, handler: H) -> Self
where
TextLike: Into<Text>,
Res: IntoResponse,
H: PuffHandler<T1, S, Res>,
{
self.on(MethodFilter::POST, path, handler)
}
pub fn head<TextLike, H, T1, Res>(self, path: TextLike, handler: H) -> Self
where
TextLike: Into<Text>,
Res: IntoResponse,
H: PuffHandler<T1, S, Res>,
{
self.on(MethodFilter::HEAD, path, handler)
}
pub fn options<TextLike, H, T1, Res>(self, path: TextLike, handler: H) -> Self
where
TextLike: Into<Text>,
Res: IntoResponse,
H: PuffHandler<T1, S, Res>,
{
self.on(MethodFilter::OPTIONS, path, handler)
}
pub fn put<TextLike, H, T1, Res>(self, path: TextLike, handler: H) -> Self
where
TextLike: Into<Text>,
Res: IntoResponse,
H: PuffHandler<T1, S, Res>,
{
self.on(MethodFilter::PUT, path, handler)
}
pub fn patch<TextLike, H, T1, Res>(self, path: TextLike, handler: H) -> Self
where
TextLike: Into<Text>,
Res: IntoResponse,
H: PuffHandler<T1, S, Res>,
{
self.on(MethodFilter::PATCH, path, handler)
}
pub fn trace<TextLike, H, T1, Res>(self, path: TextLike, handler: H) -> Self
where
TextLike: Into<Text>,
Res: IntoResponse,
H: PuffHandler<T1, S, Res>,
{
self.on(MethodFilter::TRACE, path, handler)
}
pub fn delete<TextLike, H, T1, Res>(self, path: TextLike, handler: H) -> Self
where
TextLike: Into<Text>,
Res: IntoResponse,
H: PuffHandler<T1, S, Res>,
{
self.on(MethodFilter::DELETE, path, handler)
}
pub fn any<TextLike, H, T1, Res>(self, path: TextLike, handler: H) -> Self
where
TextLike: Into<Text>,
Res: IntoResponse,
H: PuffHandler<T1, S, Res>,
{
self.on(MethodFilter::all(), path, handler)
}
pub fn service<TextLike, T>(self, path: TextLike, f: T) -> Self
where
TextLike: Into<Text>,
T: Service<AxumRequest<Body>, Error = Infallible> + Clone + Send + 'static,
T::Response: IntoResponse + 'static,
T::Future: Send + 'static,
{
Self(self.0.route(&path.into(), any_service(f)))
}
pub fn layer<L>(self, layer: L) -> Self
where
L: tower_layer::Layer<Route>,
L::Service: Service<Request> + Clone + Send + 'static,
<L::Service as Service<Request>>::Response: IntoResponse + 'static,
<L::Service as Service<Request>>::Error: Into<Infallible> + 'static,
<L::Service as Service<Request>>::Future: Send + 'static,
{
Self(self.0.layer(layer))
}
pub(crate) fn into_axum_router(self, puff_context: PuffContext) -> axum::Router<S> {
self.0.layer(Extension(puff_context)).clone()
}
pub fn into_hyper_server(
self,
addr: &SocketAddr,
puff_context: PuffContext,
) -> axum::Server<AddrIncoming, IntoMakeService<axum::Router<S>>> {
let new_router = self.into_axum_router(puff_context);
axum::Server::bind(addr).serve(new_router.into_make_service())
}
}
pub fn body_iter_bytes<
B: Into<Bytes> + 'static,
I: IntoIterator<Item = std::result::Result<B, std::io::Error>>,
>(
chunks: I,
) -> Body
where
<I as IntoIterator>::IntoIter: Send + 'static,
{
Body::wrap_stream(futures_util::stream::iter(chunks))
}
pub fn body_bytes<B>(chunks: B) -> Body
where
B: Into<Bytes> + 'static,
{
Body::from(chunks.into())
}
pub fn body_text<B>(chunks: B) -> Body
where
B: Into<Text> + 'static,
{
let t = chunks.into();
Body::from(Bytes::copy_from_slice(t.as_bytes()))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::RealPuffContext;
use crate::types::text::ToText;
use tokio::runtime::Runtime;
#[test]
fn check_router() {
let router: Router<()> = Router::new().get("/", || async { "ok".to_text() });
let rt = Runtime::new().unwrap();
let puff_context = RealPuffContext::empty(rt.handle().clone());
let fut = router.into_axum_router(puff_context.clone()).call(
AxumRequest::get("http://localhost/")
.body(Body::empty())
.unwrap(),
);
let result = rt.block_on(fut);
assert!(result.is_ok());
let response = result.unwrap();
println!("{:?}", response.body());
assert_eq!(response.status(), StatusCode::OK);
}
}