diff --git a/crates/rustapi-core/src/app.rs b/crates/rustapi-core/src/app.rs index a01de30..4be3f71 100644 --- a/crates/rustapi-core/src/app.rs +++ b/crates/rustapi-core/src/app.rs @@ -390,6 +390,14 @@ impl RustApi { self } + /// Add a UI route without registering OpenAPI operations. + /// + /// Use this for SSR or UI-only endpoints that should not appear in OpenAPI. + pub fn route_ui(mut self, path: &str, method_router: MethodRouter) -> Self { + self.router = self.router.route(path, method_router); + self + } + /// Add a typed route pub fn typed(self, method_router: MethodRouter) -> Self { self.route(P::PATH, method_router) @@ -438,6 +446,22 @@ impl RustApi { self.route_with_method(route.path, method_enum, route.handler) } + /// Mount a UI route created with `#[rustapi::get]`, `#[rustapi::post]`, etc. + /// + /// This skips OpenAPI registration while still mounting the handler. + pub fn mount_route_ui(self, route: crate::handler::Route) -> Self { + let method_enum = match route.method { + "GET" => http::Method::GET, + "POST" => http::Method::POST, + "PUT" => http::Method::PUT, + "DELETE" => http::Method::DELETE, + "PATCH" => http::Method::PATCH, + _ => http::Method::GET, + }; + + self.route_with_method_ui(route.path, method_enum, route.handler) + } + /// Helper to mount a single method handler fn route_with_method( self, @@ -475,6 +499,28 @@ impl RustApi { self.route(&path, method_router) } + /// Helper to mount a single method handler without OpenAPI registration + fn route_with_method_ui( + self, + path: &str, + method: http::Method, + handler: crate::handler::BoxedHandler, + ) -> Self { + use crate::router::MethodRouter; + + let path = if !path.starts_with('/') { + format!("/{}", path) + } else { + path.to_string() + }; + + let mut handlers = std::collections::HashMap::new(); + handlers.insert(method, handler); + + let method_router = MethodRouter::from_boxed(handlers); + self.route_ui(&path, method_router) + } + /// Nest a router under a prefix /// /// All routes from the nested router will be registered with the prefix @@ -1035,6 +1081,7 @@ impl Default for RustApi { mod tests { use super::RustApi; use crate::extract::{FromRequestParts, State}; + use crate::handler::Route; use crate::path_params::PathParams; use crate::request::Request; use crate::router::{get, post, Router}; @@ -1160,6 +1207,41 @@ mod tests { } } + #[test] + fn test_route_ui_excludes_openapi() { + async fn handler() -> &'static str { + "hello" + } + + let app = RustApi::new().route_ui("/", get(handler)); + assert!( + app.openapi_spec().paths.is_empty(), + "UI routes should not be added to OpenAPI spec" + ); + + let router = app.into_router(); + let routes = router.registered_routes(); + assert!(routes.contains_key("/"), "Expected UI route to be registered"); + } + + #[test] + fn test_mount_route_ui_excludes_openapi() { + async fn handler() -> &'static str { + "hello" + } + + let route = Route::new("/", "GET", handler); + let app = RustApi::new().mount_route_ui(route); + assert!( + app.openapi_spec().paths.is_empty(), + "UI routes should not be added to OpenAPI spec" + ); + + let router = app.into_router(); + let routes = router.registered_routes(); + assert!(routes.contains_key("/"), "Expected UI route to be registered"); + } + // **Feature: router-nesting, Property 11: OpenAPI Integration** // // For any nested routes with OpenAPI operations, the operations should appear diff --git a/docs/README.md b/docs/README.md index 33d68da..e220286 100644 --- a/docs/README.md +++ b/docs/README.md @@ -43,13 +43,25 @@ async fn hello(Path(name): Path) -> Json { Json(Message { greeting: format!("Hello, {name}!") }) } +async fn homepage() -> Html<&'static str> { + Html("

Welcome

") +} + #[tokio::main] async fn main() -> Result<(), Box> { - RustApi::auto().run("0.0.0.0:8080").await + RustApi::new() + // API routes show up in OpenAPI under /api/* + .route("/api/hello/{name}", get(hello)) + // UI/SSR routes are mounted without OpenAPI entries by default + .route_ui("/", get(homepage)) + .run("0.0.0.0:8080") + .await } ``` -Visit `http://localhost:8080/docs` for auto-generated Swagger UI. +Visit `http://localhost:8080/docs` for auto-generated Swagger UI. Routes under `/api/*` +are included in OpenAPI by default, while UI routes mounted with `route_ui`/`mount_route_ui` +are excluded unless you register them as API routes. ## Examples