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}