1use 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(®istry);
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}