rapid_rs/
app.rs

1use axum::{http::Method, Router};
2use std::net::SocketAddr;
3use tower_http::{cors::CorsLayer, trace::TraceLayer};
4use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
5use utoipa::OpenApi;
6
7#[cfg(feature = "swagger-ui")]
8use utoipa_swagger_ui::SwaggerUi;
9
10use crate::config::AppConfig;
11
12/// Main application builder
13pub struct App {
14    router: Router,
15    config: Option<AppConfig>,
16}
17
18impl App {
19    /// Create a new App instance
20    pub fn new() -> Self {
21        Self {
22            router: Router::new(),
23            config: None,
24        }
25    }
26
27    /// Auto-configure the application with sensible defaults:
28    /// - Loads configuration from files and environment
29    /// - Sets up structured logging with tracing
30    /// - Configures CORS with permissive defaults
31    /// - Adds health check endpoint
32    /// - Enables Swagger UI at /docs
33    pub fn auto_configure(mut self) -> Self {
34        // Initialize logging
35        tracing_subscriber::registry()
36            .with(
37                tracing_subscriber::EnvFilter::try_from_default_env()
38                    .unwrap_or_else(|_| "info,rapid_rs=debug,tower_http=debug".into()),
39            )
40            .with(tracing_subscriber::fmt::layer())
41            .init();
42
43        tracing::info!("🚀 Initializing rapid-rs application");
44
45        // Load configuration
46        let config = AppConfig::load().expect("Failed to load configuration");
47        tracing::info!("✅ Configuration loaded");
48
49        // Setup CORS
50        let cors = CorsLayer::new()
51            .allow_methods([
52                Method::GET,
53                Method::POST,
54                Method::PUT,
55                Method::DELETE,
56                Method::PATCH,
57            ])
58            .allow_origin(tower_http::cors::Any)
59            .allow_headers(tower_http::cors::Any);
60
61        // Add health endpoint
62        let health_router = Router::new().route(
63            "/health",
64            axum::routing::get(|| async {
65                axum::Json(serde_json::json!({
66                    "status": "healthy",
67                    "timestamp": chrono::Utc::now()
68                }))
69            }),
70        );
71
72        // Setup Swagger UI with a basic OpenAPI spec
73        #[derive(OpenApi)]
74        #[openapi(
75            info(
76                title = "rapid-rs API",
77                version = "0.1.0",
78                description = "API built with rapid-rs"
79            ),
80            paths(),
81            components(schemas())
82        )]
83        struct ApiDoc;
84
85        // Add Swagger UI if feature is enabled
86        #[cfg(feature = "swagger-ui")]
87        let swagger = SwaggerUi::new("/docs").url("/api-docs/openapi.json", ApiDoc::openapi());
88
89        // Build the router with middleware
90        #[cfg(feature = "swagger-ui")]
91        let router_with_docs = Router::new().merge(swagger).merge(health_router);
92
93        #[cfg(not(feature = "swagger-ui"))]
94        let router_with_docs = health_router;
95
96        self.router = router_with_docs
97            .merge(self.router)
98            .layer(TraceLayer::new_for_http())
99            .layer(cors);
100
101        self.config = Some(config);
102
103        tracing::info!("✅ Auto-configuration complete");
104        self
105    }
106
107    /// Mount additional routes
108    pub fn mount(mut self, router: Router) -> Self {
109        self.router = self.router.merge(router);
110        self
111    }
112
113    /// Add a route manually
114    pub fn route(mut self, path: &str, method_router: axum::routing::MethodRouter) -> Self {
115        self.router = self.router.route(path, method_router);
116        self
117    }
118
119    /// Run the application
120    pub async fn run(self) -> Result<(), Box<dyn std::error::Error>> {
121        let config = self.config.unwrap_or_default();
122        let addr = SocketAddr::from(([0, 0, 0, 0], config.server.port));
123
124        tracing::info!("🎯 Server starting on http://{}", addr);
125
126        #[cfg(feature = "swagger-ui")]
127        tracing::info!("📚 Swagger UI available at http://{}/docs", addr);
128
129        #[cfg(not(feature = "swagger-ui"))]
130        tracing::info!("💡 Tip: Enable 'swagger-ui' feature for API docs at /docs");
131
132        tracing::info!("💚 Health check available at http://{}/health", addr);
133
134        let listener = tokio::net::TcpListener::bind(addr).await?;
135        axum::serve(listener, self.router).await?;
136
137        Ok(())
138    }
139}
140
141impl Default for App {
142    fn default() -> Self {
143        Self::new()
144    }
145}