pub use xitca_router::{params::Params, MatchError};
use core::{fmt, marker::PhantomData};
use std::{borrow::Cow, collections::HashMap, error};
use xitca_service::{
object::{BoxedServiceObject, BoxedSyncServiceObject},
pipeline::PipelineT,
ready::ReadyService,
FnService, Service,
};
use crate::http::{BorrowReq, BorrowReqMut, Request, Uri};
use super::{
handler::HandlerService,
route::{MethodNotAllowed, Route},
};
pub struct Router<Obj> {
routes: HashMap<Cow<'static, str>, Obj>,
}
pub enum RouterError<E> {
Match(MatchError),
NotAllowed(MethodNotAllowed),
Service(E),
}
impl<E> fmt::Debug for RouterError<E>
where
E: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Match(ref e) => fmt::Debug::fmt(e, f),
Self::NotAllowed(ref e) => fmt::Debug::fmt(e, f),
Self::Service(ref e) => fmt::Debug::fmt(e, f),
}
}
}
impl<E> fmt::Display for RouterError<E>
where
E: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Match(ref e) => fmt::Display::fmt(e, f),
Self::NotAllowed(ref e) => fmt::Display::fmt(e, f),
Self::Service(ref e) => fmt::Display::fmt(e, f),
}
}
}
impl<E> error::Error for RouterError<E> where E: error::Error {}
impl<Obj> Default for Router<Obj> {
fn default() -> Self {
Router::new()
}
}
impl<Obj> Router<Obj> {
pub fn new() -> Self {
Router { routes: HashMap::new() }
}
}
impl<Obj> Router<Obj> {
pub fn insert<F, Arg, Req>(mut self, path: &'static str, mut builder: F) -> Self
where
F: Service<Arg> + RouterGen + Send + Sync,
F::Response: Service<Req>,
Req: IntoObject<F::Route<F>, Arg, Object = Obj>,
{
let path = builder.path_gen(path);
assert!(self
.routes
.insert(path, Req::into_object(F::route_gen(builder)))
.is_none());
self
}
#[doc(hidden)]
pub fn insert_typed<T, M>(mut self, _: T) -> Router<Obj>
where
T: TypedRoute<M, Route = Obj>,
{
let path = T::path();
let route = T::route();
assert!(self.routes.insert(Cow::Borrowed(path), route).is_none());
self
}
}
pub trait RouterGen {
type Route<R>;
fn path_gen(&mut self, prefix: &'static str) -> Cow<'static, str> {
Cow::Borrowed(prefix)
}
fn route_gen<R>(route: R) -> Self::Route<R>;
}
impl<Obj> RouterGen for Router<Obj> {
type Route<R> = R;
fn path_gen(&mut self, prefix: &'static str) -> Cow<'static, str> {
let mut path = String::from(prefix);
if path.ends_with('/') {
path.pop();
}
self.routes = self
.routes
.drain()
.map(|(k, v)| {
let mut path = path.clone();
path.push_str(k.as_ref());
(Cow::Owned(path), v)
})
.collect();
path.push_str("/*");
Cow::Owned(path)
}
fn route_gen<R>(route: R) -> Self::Route<R> {
route
}
}
impl<R, N, const M: usize> RouterGen for Route<R, N, M> {
type Route<R1> = R1;
fn route_gen<R1>(route: R1) -> Self::Route<R1> {
route
}
}
impl<F, T, O, M> RouterGen for HandlerService<F, T, O, M> {
type Route<R> = RouterMapErr<R>;
fn route_gen<R>(route: R) -> Self::Route<R> {
RouterMapErr(route)
}
}
impl<F> RouterGen for FnService<F> {
type Route<R1> = RouterMapErr<R1>;
fn route_gen<R1>(route: R1) -> Self::Route<R1> {
RouterMapErr(route)
}
}
impl<F, S, M> RouterGen for PipelineT<F, S, M>
where
F: RouterGen,
{
type Route<R> = F::Route<R>;
fn path_gen(&mut self, prefix: &'static str) -> Cow<'static, str> {
self.first.path_gen(prefix)
}
fn route_gen<R>(route: R) -> Self::Route<R> {
F::route_gen(route)
}
}
pub struct RouterMapErr<S>(pub S);
impl<S, Arg> Service<Arg> for RouterMapErr<S>
where
S: Service<Arg>,
{
type Response = MapErrService<S::Response>;
type Error = S::Error;
async fn call(&self, arg: Arg) -> Result<Self::Response, Self::Error> {
self.0.call(arg).await.map(MapErrService)
}
}
pub struct MapErrService<S>(S);
impl<S, Req> Service<Req> for MapErrService<S>
where
S: Service<Req>,
{
type Response = S::Response;
type Error = RouterError<S::Error>;
#[inline]
async fn call(&self, req: Req) -> Result<Self::Response, Self::Error> {
self.0.call(req).await.map_err(RouterError::Service)
}
}
impl<Obj, Arg> Service<Arg> for Router<Obj>
where
Obj: Service<Arg>,
Arg: Clone,
{
type Response = RouterService<Obj::Response>;
type Error = Obj::Error;
async fn call(&self, arg: Arg) -> Result<Self::Response, Self::Error> {
let mut routes = xitca_router::Router::new();
for (path, service) in self.routes.iter() {
let service = service.call(arg.clone()).await?;
routes.insert(path.to_string(), service).unwrap();
}
Ok(RouterService { routes })
}
}
pub struct RouterService<S> {
routes: xitca_router::Router<S>,
}
impl<S, Req, E> Service<Req> for RouterService<S>
where
S: Service<Req, Error = RouterError<E>>,
Req: BorrowReq<Uri> + BorrowReqMut<Params>,
{
type Response = S::Response;
type Error = S::Error;
#[allow(clippy::manual_async_fn)]
#[inline]
fn call(&self, mut req: Req) -> impl core::future::Future<Output = Result<Self::Response, Self::Error>> {
async {
let xitca_router::Match { value, params } =
self.routes.at(req.borrow().path()).map_err(RouterError::Match)?;
*req.borrow_mut() = params;
Service::call(value, req).await
}
}
}
impl<S> ReadyService for RouterService<S> {
type Ready = ();
#[inline]
async fn ready(&self) -> Self::Ready {}
}
pub trait IntoObject<I, Arg> {
type Object;
fn into_object(inner: I) -> Self::Object;
}
impl<T, Arg, Ext, Res, Err> IntoObject<T, Arg> for Request<Ext>
where
Ext: 'static,
T: Service<Arg> + Send + Sync + 'static,
T::Response: Service<Request<Ext>, Response = Res, Error = Err> + 'static,
{
type Object = BoxedSyncServiceObject<Arg, BoxedServiceObject<Request<Ext>, Res, Err>, T::Error>;
fn into_object(inner: T) -> Self::Object {
struct Builder<T, Req>(T, PhantomData<fn(Req)>);
impl<T, Req, Arg, Res, Err> Service<Arg> for Builder<T, Req>
where
T: Service<Arg> + 'static,
T::Response: Service<Req, Response = Res, Error = Err> + 'static,
{
type Response = BoxedServiceObject<Req, Res, Err>;
type Error = T::Error;
async fn call(&self, arg: Arg) -> Result<Self::Response, Self::Error> {
self.0.call(arg).await.map(|s| Box::new(s) as _)
}
}
Box::new(Builder(inner, PhantomData))
}
}
#[doc(hidden)]
pub trait TypedRoute<M = ()> {
type Route;
fn path() -> &'static str;
fn route() -> Self::Route;
}
#[cfg(test)]
mod test {
use core::convert::Infallible;
use xitca_service::{fn_service, Service, ServiceExt};
use xitca_unsafe_collection::futures::NowOrPanic;
use crate::{
http::{Request, RequestExt, Response},
util::service::route::get,
};
use super::*;
async fn enclosed<S, Req>(service: &S, req: Req) -> Result<S::Response, S::Error>
where
S: Service<Req>,
{
service.call(req).await
}
async fn func(_: Request<RequestExt<()>>) -> Result<Response<()>, Infallible> {
Ok(Response::new(()))
}
#[test]
fn router_sync() {
fn bound_check<T: Send + Sync>(_: T) {}
bound_check(Router::new().insert("/", fn_service(func)))
}
#[test]
fn router_accept_request() {
Router::new()
.insert("/", fn_service(func))
.call(())
.now_or_panic()
.unwrap()
.call(Request::default())
.now_or_panic()
.unwrap();
}
#[test]
fn router_enclosed_fn() {
Router::new()
.insert("/", fn_service(func))
.enclosed_fn(enclosed)
.call(())
.now_or_panic()
.unwrap()
.call(Request::default())
.now_or_panic()
.unwrap();
}
#[test]
fn router_add_params_http() {
let req = Request::builder().uri("/users/1").body(Default::default()).unwrap();
Router::new()
.insert(
"/users/:id",
fn_service(|req: Request<RequestExt<()>>| async move {
let params = req.body().params();
assert_eq!(params.get("id").unwrap(), "1");
Ok::<_, Infallible>(Response::new(()))
}),
)
.enclosed_fn(enclosed)
.call(())
.now_or_panic()
.unwrap()
.call(req)
.now_or_panic()
.unwrap();
}
#[test]
fn router_nest() {
let handler = || get(fn_service(func)).enclosed_fn(enclosed);
let router = || Router::new().insert("/nest", handler()).enclosed_fn(enclosed);
let req = || {
Request::builder()
.uri("http://foo.bar/scope/nest")
.body(Default::default())
.unwrap()
};
Router::new()
.insert("/raw", fn_service(func))
.insert("/root", handler())
.insert("/scope", router())
.call(())
.now_or_panic()
.unwrap()
.call(req())
.now_or_panic()
.unwrap();
Router::new()
.insert("/root", handler())
.insert("/scope/", router())
.call(())
.now_or_panic()
.unwrap()
.call(req())
.now_or_panic()
.unwrap();
}
#[test]
fn router_service_call_size() {
let service = Router::new()
.insert("/", fn_service(func))
.call(())
.now_or_panic()
.unwrap();
println!(
"router service ready call size: {:?}",
core::mem::size_of_val(&service.ready())
);
println!(
"router service call size: {:?}",
core::mem::size_of_val(&service.call(Request::default()))
);
}
#[test]
fn router_typed() {
type Req = Request<RequestExt<()>>;
type Route = BoxedServiceObject<Req, Response<()>, RouterError<Infallible>>;
type RouteObject = BoxedSyncServiceObject<(), Route, Infallible>;
struct Index;
impl TypedRoute for Index {
type Route = RouteObject;
fn path() -> &'static str {
"/"
}
fn route() -> Self::Route {
Req::into_object(RouterMapErr(xitca_service::fn_service(func)))
}
}
struct V2;
impl TypedRoute for V2 {
type Route = RouteObject;
fn path() -> &'static str {
"/v2"
}
fn route() -> Self::Route {
Req::into_object(RouterMapErr(xitca_service::fn_service(func)))
}
}
Router::new()
.insert_typed(Index)
.insert_typed(V2)
.call(())
.now_or_panic()
.unwrap()
.call(Request::default())
.now_or_panic()
.unwrap();
}
}