Skip to main content

anvil_core/
app.rs

1//! Application builder. Mirrors Laravel 11's `bootstrap/app.rs`.
2
3use std::net::SocketAddr;
4
5use axum::Router as AxumRouter;
6use tower_http::trace::TraceLayer;
7
8use crate::container::{Container, ContainerBuilder};
9use crate::middleware::{install_defaults, MiddlewareRegistry};
10use crate::route::Router;
11use crate::shutdown::ShutdownHandle;
12
13pub struct Application {
14    pub container: Container,
15    pub registry: MiddlewareRegistry,
16    pub web: AxumRouter<Container>,
17    pub api: AxumRouter<Container>,
18    pub shutdown: ShutdownHandle,
19}
20
21pub struct ApplicationBuilder {
22    container_builder: ContainerBuilder,
23    registry: MiddlewareRegistry,
24    web_routes: Option<Box<dyn FnOnce(Router) -> Router>>,
25    api_routes: Option<Box<dyn FnOnce(Router) -> Router>>,
26}
27
28impl Default for ApplicationBuilder {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl ApplicationBuilder {
35    pub fn new() -> Self {
36        let registry = MiddlewareRegistry::new();
37        install_defaults(&registry);
38        Self {
39            container_builder: ContainerBuilder::from_env(),
40            registry,
41            web_routes: None,
42            api_routes: None,
43        }
44    }
45
46    pub fn container<F>(mut self, configure: F) -> Self
47    where
48        F: FnOnce(ContainerBuilder) -> ContainerBuilder,
49    {
50        self.container_builder = configure(self.container_builder);
51        self
52    }
53
54    pub fn middleware<F>(self, configure: F) -> Self
55    where
56        F: FnOnce(&MiddlewareRegistry),
57    {
58        configure(&self.registry);
59        self
60    }
61
62    pub fn web<F>(mut self, build: F) -> Self
63    where
64        F: FnOnce(Router) -> Router + 'static,
65    {
66        self.web_routes = Some(Box::new(build));
67        self
68    }
69
70    pub fn api<F>(mut self, build: F) -> Self
71    where
72        F: FnOnce(Router) -> Router + 'static,
73    {
74        self.api_routes = Some(Box::new(build));
75        self
76    }
77
78    pub fn build(self) -> Application {
79        let container = self.container_builder.build();
80        let registry = self.registry;
81
82        let web_router = self.web_routes.map(|f| {
83            let router = Router::new(registry.clone());
84            f(router).with_state()
85        });
86
87        let api_router = self.api_routes.map(|f| {
88            let router = Router::new(registry.clone()).prefix("/api");
89            f(router).with_state()
90        });
91
92        Application {
93            container,
94            registry,
95            web: web_router.unwrap_or_else(AxumRouter::new),
96            api: api_router.unwrap_or_else(AxumRouter::new),
97            shutdown: ShutdownHandle::new(),
98        }
99    }
100}
101
102impl Application {
103    pub fn builder() -> ApplicationBuilder {
104        ApplicationBuilder::new()
105    }
106
107    pub fn into_router(self) -> AxumRouter {
108        let combined = self
109            .web
110            .merge(self.api)
111            .layer(TraceLayer::new_for_http())
112            .with_state(self.container.clone());
113        combined
114    }
115
116    pub async fn serve(self, addr: SocketAddr) -> Result<(), crate::Error> {
117        let shutdown = self.shutdown.clone().install();
118        let listener = tokio::net::TcpListener::bind(addr).await?;
119        tracing::info!(%addr, "anvil listening");
120
121        let router = self.into_router();
122        axum::serve(listener, router)
123            .with_graceful_shutdown(async move { shutdown.wait().await })
124            .await?;
125        Ok(())
126    }
127}