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
19pub struct App {
21 router: Router,
22 config: Option<AppConfig>,
23}
24
25impl App {
26 pub fn new() -> Self {
28 Self {
29 router: Router::new(),
30 config: None,
31 }
32 }
33
34 pub fn auto_configure(mut self) -> Self {
41 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 let config = AppConfig::load().expect("Failed to load configuration");
54 tracing::info!("✅ Configuration loaded");
55
56 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 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 #[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 #[cfg(feature = "swagger-ui")]
86 let swagger = SwaggerUi::new("/docs").url("/api-docs/openapi.json", ApiDoc::openapi());
87
88 #[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 pub fn mount(mut self, router: Router) -> Self {
110 self.router = self.router.merge(router);
111 self
112 }
113
114 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 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}