doido_controller/
server.rs1use crate::config::{self, ServerConfig};
4use crate::stack::MiddlewareStack;
5
6fn resolve_addr(server: &ServerConfig, bind: Option<&str>, port: Option<u16>) -> String {
9 let bind = bind.unwrap_or(&server.bind);
10 let port = port.unwrap_or(server.port);
11 format!("{bind}:{port}")
12}
13
14fn api_only() -> bool {
20 std::fs::read_to_string("config/application.toml")
21 .map(|contents| marker_is_api_only(&contents))
22 .unwrap_or(false)
23}
24
25fn marker_is_api_only(contents: &str) -> bool {
28 contents.lines().any(|line| {
29 let line = line.split('#').next().unwrap_or("").trim();
30 matches!(line.split_once('='), Some((k, v)) if k.trim() == "api_only" && v.trim().eq_ignore_ascii_case("true"))
31 })
32}
33
34pub async fn start_server(router: crate::axum::Router) -> std::io::Result<()> {
38 start_server_with(router, None, None).await
39}
40
41pub async fn start_server_with(
44 router: crate::axum::Router,
45 bind: Option<String>,
46 port: Option<u16>,
47) -> std::io::Result<()> {
48 let config = config::load();
49 let addr = resolve_addr(config.server(), bind.as_deref(), port);
50
51 let router = MiddlewareStack::default()
55 .with_api_only(api_only())
56 .apply(router);
57
58 let listener = tokio::net::TcpListener::bind(&addr).await?;
59 tracing::info!("listening on http://{addr}");
60 tracing::info!("routes:\n{}", crate::route_table::format_routes());
61 crate::axum::serve(listener, router).await
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn resolve_addr_prefers_overrides_then_config() {
70 let server = ServerConfig {
71 bind: "0.0.0.0".into(),
72 port: 3000,
73 };
74 assert_eq!(resolve_addr(&server, None, None), "0.0.0.0:3000");
75 assert_eq!(resolve_addr(&server, None, Some(4000)), "0.0.0.0:4000");
76 assert_eq!(
77 resolve_addr(&server, Some("127.0.0.1"), None),
78 "127.0.0.1:3000"
79 );
80 assert_eq!(
81 resolve_addr(&server, Some("127.0.0.1"), Some(8080)),
82 "127.0.0.1:8080"
83 );
84 }
85
86 #[test]
87 fn marker_detects_api_only() {
88 assert!(marker_is_api_only("[app]\nname = \"x\"\napi_only = true\n"));
89 assert!(marker_is_api_only("api_only = TRUE # marker"));
90 assert!(!marker_is_api_only("[app]\nname = \"x\"\n"));
91 assert!(!marker_is_api_only("api_only = false"));
92 assert!(!marker_is_api_only("# api_only = true"));
93 }
94}