mas_config/sections/
room_invites.rs1use schemars::JsonSchema;
7use serde::{Deserialize, Serialize, de::Error};
8use url::Url;
9
10use crate::ConfigurationSection;
11
12#[derive(Clone, Debug, Default, Deserialize, JsonSchema, Serialize)]
14pub struct RoomInvitesConfig {
15 #[serde(skip_serializing_if = "Option::is_none")]
19 pub client_room_url: Option<String>,
20}
21
22impl RoomInvitesConfig {
23 pub(crate) fn is_default(&self) -> bool {
25 self.client_room_url.is_none()
26 }
27}
28
29impl ConfigurationSection for RoomInvitesConfig {
30 const PATH: Option<&'static str> = Some("room_invites");
31
32 fn validate(
33 &self,
34 figment: &figment::Figment,
35 ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
36 let metadata = figment.find_metadata(Self::PATH.unwrap());
37
38 let error = |message: &str| -> Box<dyn std::error::Error + Send + Sync + 'static> {
39 let mut error = figment::error::Error::custom(message);
40 error.metadata = metadata.cloned();
41 error.profile = Some(figment::Profile::Default);
42 error.path = vec![Self::PATH.unwrap().to_owned(), "client_room_url".to_owned()];
43 error.into()
44 };
45
46 match &self.client_room_url {
47 Some(url) if !url.contains("{room_id}") => {
48 return Err(error("must contain `{room_id}`"));
49 }
50 Some(url)
51 if !Url::parse(&url.replace("{room_id}", "room")).is_ok_and(|url| {
52 matches!(url.scheme(), "http" | "https") && url.has_host()
53 }) =>
54 {
55 return Err(error("must be an absolute HTTP or HTTPS URL"));
56 }
57 _ => {}
58 }
59
60 Ok(())
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 #![expect(clippy::result_large_err)]
69
70 use figment::{
71 Figment, Jail,
72 providers::{Format, Yaml},
73 };
74
75 use super::*;
76
77 fn load(jail: &mut Jail, yaml: &str) -> Result<RoomInvitesConfig, String> {
78 jail.create_file("config.yaml", yaml)
79 .map_err(|e| e.to_string())?;
80 let figment = Figment::new().merge(Yaml::file("config.yaml"));
81 RoomInvitesConfig::extract(&figment).map_err(|e| e.to_string())
82 }
83
84 #[test]
85 fn defaults() {
86 Jail::expect_with(|jail| {
87 let config = load(jail, "room_invites: {}").unwrap();
88 assert_eq!(config.client_room_url, None);
89 assert!(config.is_default());
90 Ok(())
91 });
92 }
93
94 #[test]
95 fn client_room_url_requires_room_id() {
96 Jail::expect_with(|jail| {
97 let error = load(
98 jail,
99 "room_invites: { client_room_url: 'https://app.example.com/' }",
100 )
101 .unwrap_err();
102 assert!(error.contains("{room_id}"), "{error}");
103 Ok(())
104 });
105 }
106
107 #[test]
108 fn client_room_url_must_be_absolute() {
109 Jail::expect_with(|jail| {
110 for url in [
111 "app.example.com/#/room/{room_id}",
112 "localhost:8080/#/room/{room_id}",
113 "ftp://app.example.com/#/room/{room_id}",
114 "https:/#/room/{room_id}",
115 ] {
116 let error = load(
117 jail,
118 &format!("room_invites: {{ client_room_url: '{url}' }}"),
119 )
120 .unwrap_err();
121 assert!(
122 error.contains("absolute HTTP or HTTPS URL"),
123 "{url}: {error}"
124 );
125 }
126 Ok(())
127 });
128 }
129}