use crate::routers::ServerRouter;
use reinhardt_core::endpoint::EndpointInfo;
use reinhardt_http::Handler;
use reinhardt_middleware::Middleware;
pub type RouteInfo = Vec<(String, Option<String>, Option<String>, Vec<hyper::Method>)>;
pub struct RouteGroup {
router: ServerRouter,
}
impl RouteGroup {
pub fn new() -> Self {
Self {
router: ServerRouter::new(),
}
}
pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
self.router = self.router.with_prefix(prefix);
self
}
pub fn with_namespace(mut self, namespace: impl Into<String>) -> Self {
self.router = self.router.with_namespace(namespace);
self
}
pub fn with_middleware<M: Middleware + 'static>(mut self, middleware: M) -> Self {
self.router = self.router.with_middleware(middleware);
self
}
pub fn endpoint<F, E>(mut self, f: F) -> Self
where
F: FnOnce() -> E,
E: EndpointInfo + Handler + 'static,
{
self.router = self.router.endpoint(f);
self
}
#[cfg(feature = "viewsets")]
pub fn viewset<V: reinhardt_views::viewsets::ViewSet + 'static>(
mut self,
prefix: &str,
viewset: V,
) -> Self {
self.router = self.router.viewset(prefix, viewset);
self
}
#[cfg(feature = "viewsets")]
pub fn viewset_with_actions<V, M>(
self,
prefix: &str,
viewset: V,
_marker: std::marker::PhantomData<M>,
) -> Self
where
V: reinhardt_views::viewsets::ViewSet + 'static,
M: 'static,
{
reinhardt_views::viewsets::bridge_marker_actions_to_viewset(
std::any::type_name::<M>(),
std::any::type_name::<V>(),
);
self.viewset(prefix, viewset)
}
pub fn view<V>(mut self, path: &str, view: V) -> Self
where
V: reinhardt_http::Handler + 'static,
{
self.router = self.router.view(path, view);
self
}
#[deprecated(
since = "0.2.0",
note = "Use `#[get(\"/path\", name = \"name\")]` + `.endpoint()` instead"
)]
pub fn view_named<V>(mut self, path: &str, name: &str, view: V) -> Self
where
V: reinhardt_http::Handler + 'static,
{
#[allow(deprecated)]
{
self.router = self.router.view_named(path, name, view);
}
self
}
pub fn nest(mut self, child: RouteGroup) -> Self {
let child_prefix = child.router.prefix().to_string();
self.router = self.router.mount(&child_prefix, child.router);
self
}
pub fn prefix(&self) -> &str {
self.router.prefix()
}
pub fn namespace(&self) -> Option<&str> {
self.router.namespace()
}
pub fn children_count(&self) -> usize {
self.router.children_count()
}
pub fn get_all_routes(&self) -> RouteInfo {
self.router.get_all_routes()
}
pub fn build(self) -> ServerRouter {
self.router
}
}
impl Default for RouteGroup {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use hyper::Method;
use reinhardt_http::{Request, Response, Result};
use reinhardt_middleware::LoggingMiddleware;
struct TestEndpoint<const ID: u8>;
impl<const ID: u8> EndpointInfo for TestEndpoint<ID> {
fn path() -> &'static str {
match ID {
1 => "/health",
2 => "/login",
3 => "/test",
_ => unreachable!("unsupported test endpoint"),
}
}
fn method() -> Method {
match ID {
2 => Method::POST,
_ => Method::GET,
}
}
fn name() -> &'static str {
match ID {
1 => "health",
2 => "login",
3 => "test",
_ => unreachable!("unsupported test endpoint"),
}
}
}
#[async_trait::async_trait]
impl<const ID: u8> Handler for TestEndpoint<ID> {
async fn handle(&self, _req: Request) -> Result<Response> {
Ok(Response::ok())
}
}
#[test]
fn test_route_group_new() {
let group = RouteGroup::new();
let router = group.build();
assert_eq!(router.prefix(), "");
}
#[test]
fn test_route_group_with_prefix() {
let group = RouteGroup::new().with_prefix("/api/v1");
let router = group.build();
assert_eq!(router.prefix(), "/api/v1");
}
#[test]
fn test_route_group_with_namespace() {
let group = RouteGroup::new().with_namespace("v1");
let router = group.build();
assert_eq!(router.namespace(), Some("v1"));
}
#[test]
fn test_route_group_with_middleware() {
let group = RouteGroup::new().with_middleware(LoggingMiddleware::new());
let _router = group.build();
}
#[test]
fn test_route_group_endpoint() {
let group = RouteGroup::new().endpoint(|| TestEndpoint::<1>);
let _router = group.build();
}
#[test]
fn test_route_group_nested() {
let auth_group = RouteGroup::new()
.with_prefix("/auth/")
.endpoint(|| TestEndpoint::<2>);
let group = RouteGroup::new().with_prefix("/api/").nest(auth_group);
let router = group.build();
assert_eq!(router.children_count(), 1);
}
#[test]
fn test_route_group_multiple_middleware() {
let group = RouteGroup::new()
.with_middleware(LoggingMiddleware::new())
.with_middleware(LoggingMiddleware::new())
.endpoint(|| TestEndpoint::<3>);
let _router = group.build();
}
}
#[cfg(all(test, feature = "viewsets"))]
mod viewset_with_actions_tests {
use super::*;
use async_trait::async_trait;
use reinhardt_http::{Request, Response, Result};
use reinhardt_views::viewsets::{Action, ViewSet};
use rstest::rstest;
use std::marker::PhantomData;
#[derive(Debug, Clone)]
struct DummyViewSet {
basename: String,
}
#[async_trait]
impl ViewSet for DummyViewSet {
fn get_basename(&self) -> &str {
&self.basename
}
async fn dispatch(&self, _request: Request, _action: Action) -> Result<Response> {
Ok(Response::ok())
}
}
struct DummyImpl;
#[rstest]
fn viewset_with_actions_is_equivalent_to_viewset() {
let group_a = RouteGroup::new().viewset(
"/users",
DummyViewSet {
basename: "users".to_string(),
},
);
let group_b = RouteGroup::new().viewset_with_actions(
"/users",
DummyViewSet {
basename: "users".to_string(),
},
PhantomData::<DummyImpl>,
);
let mut router_a = group_a.build();
let mut router_b = group_b.build();
let _ = router_a.register_all_routes();
let _ = router_b.register_all_routes();
let routes_a = router_a.get_all_routes();
let routes_b = router_b.get_all_routes();
assert_eq!(routes_a, routes_b);
}
}