use std::{collections::HashMap, fmt, fs::File, io::BufReader}; use anyhow::Context; use matrix_sdk::ruma::{OwnedRoomOrAliasId, OwnedUserId, RoomOrAliasId, UserId}; use serde::de; #[derive(Deserialize)] pub struct RepoConfig { pub token: String, #[serde(default)] #[serde(deserialize_with = "deser_optional_room_or_alias_id")] pub room: Option, } #[derive(Deserialize)] pub struct Config { pub bind_address: Option, pub bind_port: Option, #[serde(deserialize_with = "deser_user_id")] pub user_id: OwnedUserId, pub password: String, #[serde(default)] #[serde(deserialize_with = "deser_optional_room_or_alias_id")] pub default_room: Option, pub repo_configs: HashMap, // key is repo url without scheme; e.g. // gitlab.xfce.org/xfce/xfdesktop } fn deser_user_id<'de, D>(deserializer: D) -> Result where D: de::Deserializer<'de>, { struct UserIdVisitor; impl<'de> de::Visitor<'de> for UserIdVisitor { type Value = OwnedUserId; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a matrix user ID") } fn visit_str(self, v: &str) -> Result where E: de::Error, { UserId::parse(v).map_err(E::custom) } } deserializer.deserialize_any(UserIdVisitor) } fn deser_room_or_alias_id<'de, D>(deserializer: D) -> Result where D: de::Deserializer<'de>, { struct RoomOrAliasIdVisitor; impl<'de> de::Visitor<'de> for RoomOrAliasIdVisitor { type Value = OwnedRoomOrAliasId; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a matrix room ID") } fn visit_str(self, v: &str) -> Result where E: de::Error, { RoomOrAliasId::parse(v).map_err(E::custom) } } deserializer.deserialize_any(RoomOrAliasIdVisitor) } fn deser_optional_room_or_alias_id<'de, D>(deserializer: D) -> Result, D::Error> where D: de::Deserializer<'de>, { struct OptionalRoomOrAliasIdVisitor; impl<'de> de::Visitor<'de> for OptionalRoomOrAliasIdVisitor { type Value = Option; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("null or matrix room ID") } fn visit_none(self) -> Result where E: de::Error, { Ok(None) } fn visit_some(self, deserializer: D) -> Result where D: serde::Deserializer<'de>, { Ok(Some(deser_room_or_alias_id(deserializer)?)) } fn visit_str(self, v: &str) -> Result where E: de::Error, { RoomOrAliasId::parse(v).map(Some).map_err(E::custom) } } deserializer.deserialize_any(OptionalRoomOrAliasIdVisitor) } fn load_blocking(path: &String) -> anyhow::Result { let f = File::open(path)?; let r = BufReader::new(f); let config: Config = serde_yaml::from_reader(r)?; Ok(config) } pub async fn load>(path: S) -> anyhow::Result { let p = String::from(path.as_ref()); let config = tokio::task::spawn_blocking(move || { load_blocking(&p).with_context(|| format!("Failed to load config from {}", p)) }) .await??; Ok(config) }