use std::sync::Arc;
use http::{Request, Response, StatusCode};
use hyper::body::Incoming;
use crate::extract::PathParams;
use crate::introspection::RouteInfo;
use crate::response::{BoxBody, IntoResponse};
use crate::state::AppState;
#[derive(Debug, Clone, Default)]
pub struct RouteRegistry {
routes: Vec<RouteInfo>,
}
impl RouteRegistry {
pub fn new() -> Self {
Self { routes: Vec::new() }
}
pub fn with_routes(routes: Vec<RouteInfo>) -> Self {
Self { routes }
}
pub fn routes(&self) -> &[RouteInfo] {
&self.routes
}
}
pub async fn list_routes(
_req: Request<Incoming>,
_params: PathParams,
state: Arc<AppState>,
) -> Response<BoxBody> {
let registry = state.get::<RouteRegistry>();
match registry {
Some(registry) => {
let json = serde_json::to_vec(registry.routes()).unwrap_or_default();
Response::builder()
.status(StatusCode::OK)
.header("content-type", "application/json")
.body(http_body_util::Full::new(bytes::Bytes::from(json)))
.unwrap()
}
None => StatusCode::NOT_FOUND.into_response(),
}
}
#[cfg(test)]
mod tests {
use http::{HeaderValue, Method};
use serde_json::Value;
use crate::{app::Rapina, router::Router, testing::TestClient};
use super::*;
#[test]
fn test_route_registry_new() {
let registry = RouteRegistry::new();
assert!(registry.routes().is_empty());
}
#[test]
fn test_route_registry_default() {
let registry = RouteRegistry::default();
assert!(registry.routes().is_empty());
}
#[test]
fn test_route_registry_with_routes() {
let routes = vec![
RouteInfo::new("GET", "/users", "list_users", None, Vec::new()),
RouteInfo::new("POST", "/users", "create_user", None, Vec::new()),
];
let registry = RouteRegistry::with_routes(routes);
assert_eq!(registry.routes().len(), 2);
}
#[test]
fn test_route_registry_clone() {
let routes = vec![RouteInfo::new("GET", "/", "index", None, Vec::new())];
let registry = RouteRegistry::with_routes(routes);
let cloned = registry.clone();
assert_eq!(registry.routes().len(), cloned.routes().len());
}
#[test]
fn test_route_registry_routes_content() {
let routes = vec![
RouteInfo::new("GET", "/health", "health_check", None, Vec::new()),
RouteInfo::new("POST", "/users", "create_user", None, Vec::new()),
];
let registry = RouteRegistry::with_routes(routes);
assert_eq!(registry.routes()[0].method, "GET");
assert_eq!(registry.routes()[0].path, "/health");
assert_eq!(registry.routes()[0].handler_name, "health_check");
assert_eq!(registry.routes()[1].method, "POST");
}
#[test]
fn test_route_registry_debug() {
let registry = RouteRegistry::new();
let debug = format!("{:?}", registry);
assert!(debug.contains("RouteRegistry"));
}
#[tokio::test]
async fn test_list_routes_returns_200_with_json_content_type() {
let router = Router::new().route(Method::GET, "/hello", |_, _, _| async { "hello" });
let app = Rapina::new().router(router).with_introspection(true);
let client = TestClient::new(app).await;
let response = client.get("/__rapina/routes").send().await;
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
response.headers().get(http::header::CONTENT_TYPE),
Some(&HeaderValue::from_static("application/json"))
);
}
#[tokio::test]
async fn test_list_routes_response_contains_registered_routes() {
let router = Router::new().route(Method::GET, "/hello", |_, _, _| async { "hello" });
let app = Rapina::new().router(router).with_introspection(true);
let client = TestClient::new(app).await;
let response = client.get("/__rapina/routes").send().await;
let json = response.json::<Value>();
assert!(json.is_array());
let route = &json[0];
assert_eq!(route["method"], "GET");
assert_eq!(route["path"], "/hello");
assert_eq!(route["handler_name"], "handler");
}
#[tokio::test]
async fn test_list_routes_returns_404_and_empty_body_when_with_introspection_disabled() {
let router = Router::new().route(Method::GET, "/hello", |_, _, _| async { "hello" });
let app = Rapina::new().router(router).with_introspection(false);
let client = TestClient::new(app).await;
let response = client.get("/__rapina/routes").send().await;
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
}