public_appservice/
config.rs

1use serde::{Deserialize, Serialize};
2use std::fs;
3use std::path::Path;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Config {
7    #[serde(default)]
8    pub server: Server,
9    pub appservice: AppService,
10    pub matrix: Matrix,
11    #[serde(default)]
12    pub redis: Redis,
13    #[serde(default)]
14    pub cache: Cache,
15    #[serde(default)]
16    pub public_rooms: PublicRooms,
17    #[serde(default)]
18    pub spaces: Spaces,
19    pub logging: Option<Logging>,
20    #[serde(default)]
21    pub search: Search,
22    #[serde(default)]
23    pub sentry: Option<Sentry>,
24    #[serde(default)]
25    pub metrics: Metrics,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct Server {
30    #[serde(default = "default_port")]
31    pub port: u16,
32    pub allow_origin: Option<Vec<String>>,
33}
34
35impl Default for Server {
36    fn default() -> Self {
37        Self {
38            port: default_port(),
39            allow_origin: None,
40        }
41    }
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct AppService {
46    pub id: String,
47    pub sender_localpart: String,
48    pub access_token: String,
49    pub hs_access_token: String,
50    #[serde(default)]
51    pub rules: AppServiceRules,
52}
53
54#[derive(Default, Debug, Clone, Serialize, Deserialize)]
55pub struct AppServiceRules {
56    #[serde(default)]
57    pub auto_join: bool,
58    #[serde(default)]
59    pub invite_by_local_user: bool,
60    #[serde(default)]
61    pub federation_domain_whitelist: Vec<String>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct Matrix {
66    pub homeserver: String,
67    pub server_name: String,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Redis {
72    #[serde(default = "default_redis_url")]
73    pub url: String,
74    #[serde(default = "default_pool_size")]
75    pub pool_size: u32,
76    #[serde(default = "default_timeout_secs")]
77    pub timeout_secs: u64,
78    #[serde(default = "default_cache_ttl")]
79    pub cache_ttl: u64,
80}
81
82impl Default for Redis {
83    fn default() -> Self {
84        Self {
85            url: default_redis_url(),
86            pool_size: default_pool_size(),
87            timeout_secs: default_timeout_secs(),
88            cache_ttl: default_cache_ttl(),
89        }
90    }
91}
92
93#[derive(Default, Debug, Clone, Serialize, Deserialize)]
94pub struct Cache {
95    #[serde(default)]
96    pub joined_rooms: CacheOptions,
97    #[serde(default)]
98    pub requests: CacheOptions,
99    #[serde(default)]
100    pub public_rooms: CacheOptions,
101    #[serde(default)]
102    pub room_state: CacheOptions,
103    #[serde(default)]
104    pub messages: CacheOptions,
105    #[serde(default)]
106    pub media: CacheOptions,
107    #[serde(default)]
108    pub search: CacheOptions,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct CacheOptions {
113    #[serde(default)]
114    pub enabled: bool,
115    #[serde(default = "default_cache_ttl")]
116    pub ttl: u64,
117}
118
119impl Default for CacheOptions {
120    fn default() -> Self {
121        Self {
122            enabled: false,
123            ttl: default_cache_ttl(),
124        }
125    }
126}
127
128#[derive(Default, Debug, Clone, Serialize, Deserialize)]
129pub struct PublicRooms {
130    #[serde(default)]
131    pub curated: bool,
132    #[serde(default)]
133    pub include_rooms: Vec<String>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct Spaces {
138    #[serde(default)]
139    pub default: Vec<String>,
140    #[serde(default)]
141    pub include_all: bool,
142    #[serde(default)]
143    pub cache: bool,
144    #[serde(default = "default_spaces_ttl")]
145    pub ttl: u64,
146}
147
148impl Default for Spaces {
149    fn default() -> Self {
150        Self {
151            default: Vec::new(),
152            include_all: false,
153            cache: false,
154            ttl: default_spaces_ttl(),
155        }
156    }
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct Logging {
161    pub directory: String,
162    pub filename: String,
163}
164
165#[derive(Default, Debug, Clone, Serialize, Deserialize)]
166pub struct Sentry {
167    pub enabled: bool,
168    pub dsn: String,
169}
170
171#[derive(Default, Debug, Clone, Serialize, Deserialize)]
172pub struct Metrics {
173    pub enabled: bool,
174    pub port: u16,
175}
176
177#[derive(Default, Debug, Clone, Serialize, Deserialize)]
178pub struct Search {
179    #[serde(default)]
180    pub disabled: bool,
181}
182
183fn default_port() -> u16 {
184    8989
185}
186
187fn default_redis_url() -> String {
188    "127.0.0.1:6379/0".to_string()
189}
190
191fn default_pool_size() -> u32 {
192    10
193}
194
195fn default_timeout_secs() -> u64 {
196    5
197}
198
199fn default_cache_ttl() -> u64 {
200    300
201}
202
203fn default_spaces_ttl() -> u64 {
204    3600
205}
206
207impl Config {
208    pub fn new(path: impl AsRef<Path>) -> Result<Self, anyhow::Error> {
209        let path = path.as_ref();
210
211        let config_content = fs::read_to_string(path)?;
212
213        let config = toml::from_str(&config_content)?;
214
215        Ok(config)
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn test_config_with_minimal_toml() {
225        let toml_content = r#"
226            [appservice]
227            id = "test"
228            sender_localpart = "bot"
229            access_token = "token"
230            hs_access_token = "hs_token"
231
232            [matrix]
233            homeserver = "http://localhost:8008"
234            server_name = "test.local"
235        "#;
236
237        let config: Config = toml::from_str(toml_content).expect("Should parse minimal config");
238
239        assert_eq!(config.server.port, 8989);
240        assert_eq!(config.redis.pool_size, 10);
241        assert!(!config.cache.requests.enabled);
242        assert!(!config.public_rooms.curated);
243    }
244}