autopulse_service/settings/
app.rs1use autopulse_utils::LogLevel;
2use serde::{Deserialize, Serialize};
3use std::net::IpAddr;
4
5fn normalize_base_path<'de, D>(deserializer: D) -> Result<String, D::Error>
8where
9 D: serde::Deserializer<'de>,
10{
11 let raw = String::deserialize(deserializer)?;
12 let trimmed = raw.trim().trim_end_matches('/');
13 if trimmed.is_empty() {
14 return Ok(String::new());
15 }
16 Ok(if trimmed.starts_with('/') {
17 trimmed.to_string()
18 } else {
19 format!("/{trimmed}")
20 })
21}
22
23#[derive(Serialize, Deserialize, Clone)]
24#[serde(default)]
25pub struct App {
26 pub hostname: String,
28 pub port: u16,
30 pub database_url: String,
32 pub log_level: LogLevel,
34 pub api_logging: bool,
36 #[serde(deserialize_with = "normalize_base_path")]
42 pub base_path: String,
43 pub secure_cookies: bool,
46 pub trusted_proxies: Vec<IpAddr>,
49}
50
51impl Default for App {
52 fn default() -> Self {
53 Self {
54 hostname: "0.0.0.0".to_string(),
55 port: 2875,
56 database_url: autopulse_database::conn::DatabaseType::default().default_url(),
57 log_level: LogLevel::default(),
58 api_logging: false,
59 base_path: String::new(),
60 secure_cookies: false,
61 trusted_proxies: Vec::new(),
62 }
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::App;
69
70 fn base_path_of(json: &str) -> String {
71 let app: App = serde_json::from_str(json).expect("valid app json");
72 app.base_path
73 }
74
75 #[test]
76 fn base_path_empty_stays_empty() {
77 assert_eq!(base_path_of(r#"{"base_path": ""}"#), "");
78 }
79
80 #[test]
81 fn base_path_lone_slash_collapses_to_empty() {
82 assert_eq!(base_path_of(r#"{"base_path": "/"}"#), "");
83 }
84
85 #[test]
86 fn base_path_missing_leading_slash_gets_one() {
87 assert_eq!(base_path_of(r#"{"base_path": "autopulse"}"#), "/autopulse");
88 }
89
90 #[test]
91 fn base_path_trailing_slash_stripped() {
92 assert_eq!(
93 base_path_of(r#"{"base_path": "/autopulse/"}"#),
94 "/autopulse"
95 );
96 }
97
98 #[test]
99 fn base_path_already_normalized_unchanged() {
100 assert_eq!(base_path_of(r#"{"base_path": "/autopulse"}"#), "/autopulse");
101 }
102
103 #[test]
104 fn base_path_trims_whitespace_and_trailing_slashes() {
105 assert_eq!(
106 base_path_of(r#"{"base_path": " /autopulse// "}"#),
107 "/autopulse"
108 );
109 }
110
111 #[test]
112 fn base_path_default_is_empty() {
113 let app: App = serde_json::from_str("{}").expect("valid empty app json");
114 assert_eq!(app.base_path, "");
115 }
116}