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
12pub struct App {
14 router: Router,
15 config: Option<AppConfig>,
16}
17
18impl App {
19 pub fn new() -> Self {
21 Self {
22 router: Router::new(),
23 config: None,
24 }
25 }
26
27 pub fn auto_configure(mut self) -> Self {
34 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 let config = AppConfig::load().expect("Failed to load configuration");
47 tracing::info!("✅ Configuration loaded");
48
49 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 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 #[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 #[cfg(feature = "swagger-ui")]
87 let swagger = SwaggerUi::new("/docs").url("/api-docs/openapi.json", ApiDoc::openapi());
88
89 #[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 pub fn mount(mut self, router: Router) -> Self {
109 self.router = self.router.merge(router);
110 self
111 }
112
113 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 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}