1use std::{any::Any, future::Future, pin::Pin, sync::Arc};
4
5use axum::{Router, extract::Extension};
6#[cfg(feature = "forgery")]
7use axum::{extract::DefaultBodyLimit, middleware::from_fn};
8use axum::{
9 http::StatusCode,
10 response::{IntoResponse, Response},
11};
12use tower_http::{catch_panic::CatchPanicLayer, services::ServeDir, trace::TraceLayer};
13
14#[cfg(feature = "forgery")]
15use crate::forgery;
16use crate::{error::Error, settings::Settings, store::Store, urls::Routes};
17
18pub struct App {
20 settings: Settings,
21 store: Arc<dyn Store>,
22 router: Router,
23 ready: Option<Ready>,
24}
25
26type Ready = Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>;
27
28impl App {
29 pub fn new(settings: Settings, store: Arc<dyn Store>) -> Self {
31 Self {
32 settings,
33 store,
34 router: Router::new(),
35 ready: None,
36 }
37 }
38
39 pub fn urls(mut self, routes: Routes) -> Self {
41 self.router = self.router.merge(routes.into_router());
42 self
43 }
44
45 pub fn mount(mut self, prefix: &str, routes: Routes) -> Self {
47 self.router = self.router.nest(prefix, routes.into_router());
48 self
49 }
50
51 pub fn mount_static(mut self) -> Self {
53 let dir = self.settings.assets.clone();
54 self.router = self.router.nest_service("/assets", ServeDir::new(dir));
55 self
56 }
57
58 pub fn ready<F, Fut>(mut self, ready: F) -> Self
60 where
61 F: FnOnce() -> Fut + Send + 'static,
62 Fut: Future<Output = ()> + Send + 'static,
63 {
64 self.ready = Some(Box::new(move || Box::pin(ready())));
65 self
66 }
67
68 fn build(self) -> Router {
69 let mut router = self
70 .router
71 .fallback(|| async { Error::NotFound.into_response() });
72 router = router.layer(Extension(self.store));
73 #[cfg(feature = "forgery")]
74 if self.settings.forgery {
75 router = router.layer(from_fn(forgery::guard));
76 }
77 router = router
78 .layer(TraceLayer::new_for_http())
79 .layer(catch_panic(self.settings.debug));
80 #[cfg(feature = "forgery")]
81 if self.settings.forgery {
82 router = router.layer(DefaultBodyLimit::max(forgery::LIMIT));
83 }
84 router
85 }
86
87 pub async fn run(mut self) -> Result<(), std::io::Error> {
89 if self.settings.secret.is_none() {
90 return Err(std::io::Error::other("set a secret before serving"));
91 }
92 let address = (self.settings.host.as_str(), self.settings.port);
93 let listener = tokio::net::TcpListener::bind(address).await?;
94 tracing::info!(
95 "rango running on http://{}:{}",
96 self.settings.host,
97 self.settings.port
98 );
99 if let Some(ready) = self.ready.take() {
100 ready().await;
101 }
102 axum::serve(listener, self.build()).await
103 }
104}
105
106fn catch_panic(
107 debug: bool,
108) -> CatchPanicLayer<impl FnMut(Box<dyn Any + Send + 'static>) -> Response + Clone> {
109 CatchPanicLayer::custom(move |panic: Box<dyn Any + Send + 'static>| {
110 let body = if debug {
111 panic
112 .downcast_ref::<&str>()
113 .map(|msg| msg.to_string())
114 .or_else(|| panic.downcast_ref::<String>().cloned())
115 .unwrap_or_else(|| "Internal Server Error".to_string())
116 } else {
117 "Internal Server Error".to_string()
118 };
119 (StatusCode::INTERNAL_SERVER_ERROR, body).into_response()
120 })
121}