pub use xitca_router::{MatchError, params::Params};
use core::{error, fmt, marker::PhantomData};
use xitca_service::{BoxFuture, FnService, Service, object::BoxedServiceObject, pipeline::PipelineT};
use crate::http::Request;
use super::{
handler::HandlerService,
route::{MethodNotAllowed, Route},
};
pub use self::object::RouteObject;
pub struct Router<Obj> {
prefix: usize,
routes: xitca_router::Router<Obj>,
}
impl<Obj> Default for Router<Obj> {
fn default() -> Self {
Router::new()
}
}
impl<Obj> Router<Obj> {
pub fn new() -> Self {
Router {
prefix: 0,
routes: xitca_router::Router::new(),
}
}
}
impl<Obj> Router<Obj> {
pub fn insert<F, Arg, Req>(mut self, path: &str, mut builder: F) -> Self
where
F: Service<Arg> + RouteGen + Send + Sync,
F::Response: Service<Req>,
Req: IntoObject<F::Route<F>, Arg, Object = Obj>,
{
let path = builder.path_gen(path);
self.routes
.insert(path, Req::into_object(F::route_gen(builder)))
.unwrap();
self
}
pub fn merge(mut self, other: Router<Obj>) -> Self {
self.routes.merge(other.routes).unwrap();
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();
self.routes.insert(path, route).unwrap();
self
}
}
impl<Obj, Arg> Service<Arg> for Router<Obj>
where
Obj: Service<Arg>,
Arg: Clone,
{
type Response = service::RouterService<Obj::Response>;
type Error = Obj::Error;
async fn call(&self, arg: Arg) -> Result<Self::Response, Self::Error> {
let mut router = xitca_router::Router::new();
for (path, service) in self.routes.entries() {
let service = service.call(arg.clone()).await?;
router.insert(path, service).unwrap();
}
Ok(service::RouterService {
prefix: self.prefix,
router,
})
}
}
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 {}
#[diagnostic::on_unimplemented(
message = "`{Self}` does not impl PathGen trait",
label = "route must impl PathGen trait for specialized route path configuration",
note = "consider add `impl PathGen for {Self}`"
)]
pub trait PathGen {
fn path_gen(&mut self, prefix: &str) -> String {
String::from(prefix)
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` does not impl RouteGen trait",
label = "route must impl RouteGen trait for specialized route path and service configuration",
note = "consider add `impl PathGen for {Self}` and `impl RouteGen for {Self}`"
)]
pub trait RouteGen: PathGen {
type Route<R>;
fn route_gen<R>(route: R) -> Self::Route<R>;
}
impl<Obj> PathGen for Router<Obj>
where
Obj: PathGen,
{
fn path_gen(&mut self, path: &str) -> String {
let mut path = String::from(path);
if path.ends_with("/{*}") {
drop(path.split_off("{*}".len()));
}
if path.ends_with('/') {
path.pop();
}
self.prefix += path.len();
self.routes.for_each_mut(|_, v| {
v.path_gen(path.as_str());
});
path.push_str("/{*}");
path
}
}
impl<Obj> RouteGen for Router<Obj>
where
Obj: RouteGen,
{
type Route<R> = R;
fn route_gen<R>(route: R) -> Self::Route<R> {
route
}
}
impl<R, N, const M: usize> PathGen for Route<R, N, M> {}
impl<R, N, const M: usize> RouteGen for Route<R, N, M> {
type Route<R1> = R1;
fn route_gen<R1>(route: R1) -> Self::Route<R1> {
route
}
}
impl<F, T, M> PathGen for HandlerService<F, T, M> {}
impl<F, T, M> RouteGen for HandlerService<F, T, M> {
type Route<R> = RouterMapErr<R>;
fn route_gen<R>(route: R) -> Self::Route<R> {
RouterMapErr(route)
}
}
impl<F> PathGen for FnService<F> {}
impl<F> RouteGen for FnService<F> {
type Route<R1> = RouterMapErr<R1>;
fn route_gen<R1>(route: R1) -> Self::Route<R1> {
RouterMapErr(route)
}
}
impl<F, S, M> PathGen for PipelineT<F, S, M>
where
F: PathGen,
{
fn path_gen(&mut self, prefix: &str) -> String {
self.first.path_gen(prefix)
}
}
impl<F, S, M> RouteGen for PipelineT<F, S, M>
where
F: RouteGen,
{
type Route<R> = F::Route<R>;
fn route_gen<R>(route: R) -> Self::Route<R> {
F::route_gen(route)
}
}
pub struct RouterMapErr<S>(pub S);
impl<S> PathGen for RouterMapErr<S>
where
S: PathGen,
{
fn path_gen(&mut self, prefix: &str) -> String {
self.0.path_gen(prefix)
}
}
impl<S> RouteGen for RouterMapErr<S>
where
S: RouteGen,
{
type Route<R> = S::Route<R>;
fn route_gen<R>(route: R) -> Self::Route<R> {
S::route_gen(route)
}
}
impl<S, Arg> Service<Arg> for RouterMapErr<S>
where
S: Service<Arg>,
{
type Response = service::MapErrService<S::Response>;
type Error = S::Error;
async fn call(&self, arg: Arg) -> Result<Self::Response, Self::Error> {
self.0.call(arg).await.map(service::MapErrService)
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` does not impl IntoObject trait",
label = "route must impl IntoObject trait for specialized type erased service type",
note = "consider add `impl IntoObject<_> for {Self}`"
)]
pub trait IntoObject<I, Arg> {
type Object;
fn into_object(inner: I) -> Self::Object;
}
mod object {
use super::*;
pub trait RouteService<Arg>: PathGen {
type Response;
type Error;
fn call<'s>(&'s self, arg: Arg) -> BoxFuture<'s, Self::Response, Self::Error>
where
Arg: 's;
}
impl<Arg, S> RouteService<Arg> for S
where
S: Service<Arg> + PathGen,
{
type Response = S::Response;
type Error = S::Error;
fn call<'s>(&'s self, arg: Arg) -> BoxFuture<'s, Self::Response, Self::Error>
where
Arg: 's,
{
Box::pin(Service::call(self, arg))
}
}
pub struct RouteObject<Arg, S, E>(pub Box<dyn RouteService<Arg, Response = S, Error = E> + Send + Sync>);
impl<Arg, S, E> PathGen for RouteObject<Arg, S, E> {
fn path_gen(&mut self, prefix: &str) -> String {
self.0.path_gen(prefix)
}
}
impl<Arg, S, E> RouteGen for RouteObject<Arg, S, E> {
type Route<R> = R;
fn route_gen<R>(route: R) -> Self::Route<R> {
route
}
}
impl<Arg, S, E> Service<Arg> for RouteObject<Arg, S, E> {
type Response = S;
type Error = E;
async fn call(&self, arg: Arg) -> Result<Self::Response, Self::Error> {
self.0.call(arg).await
}
}
}
impl<T, Arg, Ext, Res, Err> IntoObject<T, Arg> for Request<Ext>
where
Ext: 'static,
T: Service<Arg> + RouteGen + Send + Sync + 'static,
T::Response: Service<Request<Ext>, Response = Res, Error = Err> + 'static,
{
type Object = object::RouteObject<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> PathGen for Builder<T, Req>
where
T: PathGen,
{
fn path_gen(&mut self, prefix: &str) -> String {
self.0.path_gen(prefix)
}
}
impl<T, Req> RouteGen for Builder<T, Req>
where
T: RouteGen,
{
type Route<R> = T::Route<R>;
fn route_gen<R>(route: R) -> Self::Route<R> {
T::route_gen(route)
}
}
impl<T, Req, Arg, Res, Err> Service<Arg> for Builder<T, Req>
where
T: Service<Arg> + RouteGen + '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 _)
}
}
object::RouteObject(Box::new(Builder(inner, PhantomData)))
}
}
#[doc(hidden)]
pub trait TypedRoute<M = ()> {
type Route;
fn path() -> &'static str;
fn route() -> Self::Route;
}
mod service {
use xitca_service::ready::ReadyService;
use crate::http::{BorrowReq, BorrowReqMut, Uri};
use super::{Params, RouterError, Service};
pub struct RouterService<S> {
pub(super) prefix: usize,
pub(super) router: 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 Future<Output = Result<Self::Response, Self::Error>> {
async {
let path = req.borrow().path();
let xitca_router::Match { value, params } =
self.router.at(&path[self.prefix..]).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 struct MapErrService<S>(pub(super) 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)
}
}
}
#[cfg(test)]
mod test {
use core::convert::Infallible;
use xitca_service::{ServiceExt, fn_service, ready::ReadyService};
use xitca_unsafe_collection::futures::NowOrPanic;
use crate::{
http::{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();
Router::new()
.insert(
"/1111/",
Router::new().insert(
"/222222/3",
Router::new().insert("/3333333", Router::new().insert("/4444", router())),
),
)
.call(())
.now_or_panic()
.unwrap()
.call(
Request::builder()
.uri("http://foo.bar/1111/222222/3/3333333/4444/nest")
.body(Default::default())
.unwrap(),
)
.now_or_panic()
.unwrap();
Router::new()
.insert("/api", Router::new().insert("/v2", router()))
.call(())
.now_or_panic()
.unwrap()
.call(
Request::builder()
.uri("http://foo.bar/api/v2/nest")
.body(Default::default())
.unwrap(),
)
.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 Object = object::RouteObject<(), Route, Infallible>;
struct Index;
impl TypedRoute for Index {
type Route = Object;
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 = Object;
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();
}
}