rapid_rs/
app.rs

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