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 .with_cors_config(config.middleware().cors.clone())
57 .apply(router);
58
59 let listener = tokio::net::TcpListener::bind(&addr).await?;
60 tracing::info!("listening on http://{addr}");
61 tracing::info!("routes:\n{}", crate::route_table::format_routes());
62 crate::axum::serve(listener, router).await
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn resolve_addr_prefers_overrides_then_config() {
71 let server = ServerConfig {
72 bind: "0.0.0.0".into(),
73 port: 3000,
74 };
75 assert_eq!(resolve_addr(&server, None, None), "0.0.0.0:3000");
76 assert_eq!(resolve_addr(&server, None, Some(4000)), "0.0.0.0:4000");
77 assert_eq!(
78 resolve_addr(&server, Some("127.0.0.1"), None),
79 "127.0.0.1:3000"
80 );
81 assert_eq!(
82 resolve_addr(&server, Some("127.0.0.1"), Some(8080)),
83 "127.0.0.1:8080"
84 );
85 }
86
87 #[test]
88 fn marker_detects_api_only() {
89 assert!(marker_is_api_only("[app]\nname = \"x\"\napi_only = true\n"));
90 assert!(marker_is_api_only("api_only = TRUE # marker"));
91 assert!(!marker_is_api_only("[app]\nname = \"x\"\n"));
92 assert!(!marker_is_api_only("api_only = false"));
93 assert!(!marker_is_api_only("# api_only = true"));
94 }
95}