2020-09-28 21:35:05 +00:00
|
|
|
use serde::{self, Deserialize, Serialize};
|
2020-10-15 16:52:08 +00:00
|
|
|
use std::env;
|
|
|
|
use std::fs;
|
|
|
|
use std::path::PathBuf;
|
2020-10-16 12:40:25 +00:00
|
|
|
use thiserror::Error;
|
|
|
|
|
2020-10-24 13:46:06 +00:00
|
|
|
/// Shortcut to defining db migration versions. Will probably
|
|
|
|
/// eventually be moved to a config file.
|
2020-11-03 20:14:15 +00:00
|
|
|
const MIGRATION_VERSION: u32 = 5;
|
2020-10-24 13:46:06 +00:00
|
|
|
|
2020-10-16 12:40:25 +00:00
|
|
|
#[derive(Error, Debug)]
|
|
|
|
pub enum ConfigError {
|
|
|
|
#[error("i/o error: {0}")]
|
|
|
|
IoError(#[from] std::io::Error),
|
|
|
|
|
|
|
|
#[error("toml parsing error: {0}")]
|
|
|
|
TomlParsingError(#[from] toml::de::Error),
|
|
|
|
}
|
2020-10-15 16:52:08 +00:00
|
|
|
|
|
|
|
pub fn read_config<P: Into<PathBuf>>(config_path: P) -> Result<Config, ConfigError> {
|
|
|
|
let config_path = config_path.into();
|
|
|
|
let config = {
|
|
|
|
let contents = fs::read_to_string(&config_path)?;
|
|
|
|
deserialize_config(&contents)?
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn deserialize_config(contents: &str) -> Result<Config, ConfigError> {
|
|
|
|
let config = toml::from_str(&contents)?;
|
|
|
|
Ok(config)
|
|
|
|
}
|
2020-09-28 21:35:05 +00:00
|
|
|
|
|
|
|
/// The "matrix" section of the config, which gives home server, login information, and etc.
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
2020-10-03 20:31:42 +00:00
|
|
|
struct MatrixConfig {
|
2020-09-28 21:35:05 +00:00
|
|
|
/// Your homeserver of choice, as an FQDN without scheme or path
|
2020-10-03 20:31:42 +00:00
|
|
|
home_server: String,
|
2020-09-28 21:35:05 +00:00
|
|
|
|
|
|
|
/// Username to login as. Only the localpart.
|
2020-10-03 20:31:42 +00:00
|
|
|
username: String,
|
2020-09-28 21:35:05 +00:00
|
|
|
|
|
|
|
/// Bot account password.
|
2020-10-03 20:31:42 +00:00
|
|
|
password: String,
|
2020-09-28 21:35:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const DEFAULT_OLDEST_MESSAGE_AGE: u64 = 15 * 60;
|
|
|
|
|
2020-10-15 16:52:08 +00:00
|
|
|
fn db_path_from_env() -> String {
|
|
|
|
env::var("DATABASE_PATH")
|
|
|
|
.expect("could not find database path in config or environment variable")
|
|
|
|
}
|
|
|
|
|
2020-09-28 21:35:05 +00:00
|
|
|
/// The "bot" section of the config file, for bot settings.
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
2020-10-03 20:31:42 +00:00
|
|
|
struct BotConfig {
|
2020-09-28 21:35:05 +00:00
|
|
|
/// How far back from current time should we process a message?
|
|
|
|
oldest_message_age: Option<u64>,
|
|
|
|
}
|
|
|
|
|
2020-10-15 16:52:08 +00:00
|
|
|
/// The "database" section of the config file.
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
|
|
struct DatabaseConfig {
|
|
|
|
/// Path to the database storage directory. Required.
|
|
|
|
path: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DatabaseConfig {
|
|
|
|
#[inline]
|
|
|
|
#[must_use]
|
|
|
|
fn path(&self) -> String {
|
|
|
|
self.path.clone().unwrap_or_else(|| db_path_from_env())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-28 21:35:05 +00:00
|
|
|
impl BotConfig {
|
|
|
|
/// Determine the oldest allowable message age, in seconds. If the
|
|
|
|
/// setting is defined, use that value. If it is not defined, fall
|
|
|
|
/// back to DEFAULT_OLDEST_MESSAGE_AGE (15 minutes).
|
2020-10-03 20:56:00 +00:00
|
|
|
#[inline]
|
|
|
|
#[must_use]
|
2020-10-03 20:31:42 +00:00
|
|
|
fn oldest_message_age(&self) -> u64 {
|
2020-10-03 20:56:00 +00:00
|
|
|
self.oldest_message_age
|
|
|
|
.unwrap_or(DEFAULT_OLDEST_MESSAGE_AGE)
|
2020-09-28 21:35:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-03 20:31:42 +00:00
|
|
|
/// Represents the toml config file for the dicebot. The sections of
|
|
|
|
/// the config are not directly accessible; instead the config
|
|
|
|
/// provides friendly methods that handle default values, etc.
|
2020-09-28 21:35:05 +00:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
|
|
pub struct Config {
|
2020-10-03 20:31:42 +00:00
|
|
|
matrix: MatrixConfig,
|
2020-10-15 16:52:08 +00:00
|
|
|
database: Option<DatabaseConfig>,
|
2020-10-03 20:31:42 +00:00
|
|
|
bot: Option<BotConfig>,
|
2020-09-28 21:35:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Config {
|
2020-10-03 20:31:42 +00:00
|
|
|
/// The matrix homeserver URL.
|
2020-10-03 20:59:04 +00:00
|
|
|
#[inline]
|
|
|
|
#[must_use]
|
2020-10-03 20:31:42 +00:00
|
|
|
pub fn matrix_homeserver(&self) -> &str {
|
|
|
|
&self.matrix.home_server
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The username used to connect to the matrix server.
|
2020-10-03 20:59:04 +00:00
|
|
|
#[inline]
|
|
|
|
#[must_use]
|
2020-10-03 20:31:42 +00:00
|
|
|
pub fn matrix_username(&self) -> &str {
|
|
|
|
&self.matrix.username
|
|
|
|
}
|
2020-09-28 21:35:05 +00:00
|
|
|
|
2020-10-03 20:31:42 +00:00
|
|
|
/// The password used to connect to the matrix server.
|
2020-10-03 20:59:04 +00:00
|
|
|
#[inline]
|
|
|
|
#[must_use]
|
2020-10-03 20:31:42 +00:00
|
|
|
pub fn matrix_password(&self) -> &str {
|
|
|
|
&self.matrix.password
|
2020-09-28 21:35:05 +00:00
|
|
|
}
|
|
|
|
|
2020-10-15 16:52:08 +00:00
|
|
|
/// The path to the database storage directory.
|
|
|
|
#[inline]
|
|
|
|
#[must_use]
|
|
|
|
pub fn database_path(&self) -> String {
|
|
|
|
self.database
|
|
|
|
.as_ref()
|
|
|
|
.map(|db| db.path())
|
|
|
|
.unwrap_or_else(|| db_path_from_env())
|
|
|
|
}
|
|
|
|
|
2020-10-24 13:46:06 +00:00
|
|
|
/// The current migration version we expect of the database. If
|
|
|
|
/// this number is higher than the one in the database, we will
|
|
|
|
/// execute migrations to update the data.
|
|
|
|
#[inline]
|
|
|
|
#[must_use]
|
|
|
|
pub fn migration_version(&self) -> u32 {
|
|
|
|
MIGRATION_VERSION
|
|
|
|
}
|
|
|
|
|
2020-09-28 21:35:05 +00:00
|
|
|
/// Figure out the allowed oldest message age, in seconds. This will
|
|
|
|
/// be the defined oldest message age in the bot config, if the bot
|
|
|
|
/// configuration and associated "oldest_message_age" setting are
|
|
|
|
/// defined. If the bot config or the message setting are not defined,
|
2020-10-03 20:59:04 +00:00
|
|
|
/// it will default to 15 minutes.
|
|
|
|
#[inline]
|
|
|
|
#[must_use]
|
2020-10-03 20:31:42 +00:00
|
|
|
pub fn oldest_message_age(&self) -> u64 {
|
2020-09-28 21:35:05 +00:00
|
|
|
self.bot
|
|
|
|
.as_ref()
|
2020-10-03 20:31:42 +00:00
|
|
|
.map(|bc| bc.oldest_message_age())
|
|
|
|
.unwrap_or(DEFAULT_OLDEST_MESSAGE_AGE)
|
2020-09-28 21:35:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn oldest_message_default_no_setting_test() {
|
|
|
|
let cfg = Config {
|
|
|
|
matrix: MatrixConfig {
|
|
|
|
home_server: "".to_owned(),
|
|
|
|
username: "".to_owned(),
|
|
|
|
password: "".to_owned(),
|
|
|
|
},
|
2020-10-15 16:52:08 +00:00
|
|
|
database: Some(DatabaseConfig {
|
|
|
|
path: Some("".to_owned()),
|
|
|
|
}),
|
2020-09-28 21:35:05 +00:00
|
|
|
bot: Some(BotConfig {
|
|
|
|
oldest_message_age: None,
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
|
2020-10-03 20:31:42 +00:00
|
|
|
assert_eq!(15 * 60, cfg.oldest_message_age());
|
2020-09-28 21:35:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn oldest_message_default_no_bot_config_test() {
|
|
|
|
let cfg = Config {
|
|
|
|
matrix: MatrixConfig {
|
|
|
|
home_server: "".to_owned(),
|
|
|
|
username: "".to_owned(),
|
|
|
|
password: "".to_owned(),
|
|
|
|
},
|
2020-10-15 16:52:08 +00:00
|
|
|
database: Some(DatabaseConfig {
|
|
|
|
path: Some("".to_owned()),
|
|
|
|
}),
|
2020-09-28 21:35:05 +00:00
|
|
|
bot: None,
|
|
|
|
};
|
|
|
|
|
2020-10-03 20:31:42 +00:00
|
|
|
assert_eq!(15 * 60, cfg.oldest_message_age());
|
2020-09-28 21:35:05 +00:00
|
|
|
}
|
2020-10-15 16:52:08 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn db_path_uses_setting_first_test() {
|
|
|
|
let cfg = Config {
|
|
|
|
matrix: MatrixConfig {
|
|
|
|
home_server: "".to_owned(),
|
|
|
|
username: "".to_owned(),
|
|
|
|
password: "".to_owned(),
|
|
|
|
},
|
|
|
|
database: Some(DatabaseConfig {
|
|
|
|
path: Some("the-db-path".to_owned()),
|
|
|
|
}),
|
|
|
|
bot: None,
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!("the-db-path".to_owned(), cfg.database_path());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn db_path_uses_env_if_setting_not_defined_test() {
|
|
|
|
env::set_var("DATABASE_PATH", "the-db-path");
|
|
|
|
|
|
|
|
let cfg = Config {
|
|
|
|
matrix: MatrixConfig {
|
|
|
|
home_server: "".to_owned(),
|
|
|
|
username: "".to_owned(),
|
|
|
|
password: "".to_owned(),
|
|
|
|
},
|
|
|
|
database: Some(DatabaseConfig { path: None }),
|
|
|
|
bot: None,
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!("the-db-path".to_owned(), cfg.database_path());
|
|
|
|
|
|
|
|
env::remove_var("DATABASE_PATH");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn db_path_uses_env_if_section_not_defined_test() {
|
|
|
|
env::set_var("DATABASE_PATH", "the-db-path");
|
|
|
|
|
|
|
|
let cfg = Config {
|
|
|
|
matrix: MatrixConfig {
|
|
|
|
home_server: "".to_owned(),
|
|
|
|
username: "".to_owned(),
|
|
|
|
password: "".to_owned(),
|
|
|
|
},
|
|
|
|
database: None,
|
|
|
|
bot: None,
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!("the-db-path".to_owned(), cfg.database_path());
|
|
|
|
|
|
|
|
env::remove_var("DATABASE_PATH");
|
|
|
|
}
|
|
|
|
|
|
|
|
use indoc::indoc;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn deserialize_config_without_bot_section_test() {
|
|
|
|
let contents = indoc! {"
|
|
|
|
[matrix]
|
|
|
|
home_server = 'https://matrix.example.com'
|
|
|
|
username = 'username'
|
|
|
|
password = 'password'
|
|
|
|
|
|
|
|
[database]
|
|
|
|
path = ''
|
|
|
|
"};
|
|
|
|
|
|
|
|
let cfg: Result<_, _> = deserialize_config(contents);
|
|
|
|
assert_eq!(true, cfg.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn deserialize_config_without_oldest_message_setting_test() {
|
|
|
|
let contents = indoc! {"
|
|
|
|
[matrix]
|
|
|
|
home_server = 'https://matrix.example.com'
|
|
|
|
username = 'username'
|
|
|
|
password = 'password'
|
|
|
|
|
|
|
|
[database]
|
|
|
|
path = ''
|
|
|
|
|
|
|
|
[bot]
|
|
|
|
not_a_real_setting = 2
|
|
|
|
"};
|
|
|
|
|
|
|
|
let cfg: Result<_, _> = deserialize_config(contents);
|
|
|
|
assert_eq!(true, cfg.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn deserialize_config_without_db_path_setting_test() {
|
|
|
|
let contents = indoc! {"
|
|
|
|
[matrix]
|
|
|
|
home_server = 'https://matrix.example.com'
|
|
|
|
username = 'username'
|
|
|
|
password = 'password'
|
|
|
|
|
|
|
|
[database]
|
|
|
|
not_a_real_setting = 1
|
|
|
|
|
|
|
|
[bot]
|
|
|
|
not_a_real_setting = 2
|
|
|
|
"};
|
|
|
|
|
|
|
|
let cfg: Result<_, _> = deserialize_config(contents);
|
|
|
|
assert_eq!(true, cfg.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn deserialize_config_without_db_section_test() {
|
|
|
|
let contents = indoc! {"
|
|
|
|
[matrix]
|
|
|
|
home_server = 'https://matrix.example.com'
|
|
|
|
username = 'username'
|
|
|
|
password = 'password'
|
|
|
|
|
|
|
|
[bot]
|
|
|
|
not_a_real_setting = 2
|
|
|
|
"};
|
|
|
|
|
|
|
|
let cfg: Result<_, _> = deserialize_config(contents);
|
|
|
|
assert_eq!(true, cfg.is_ok());
|
|
|
|
}
|
2020-09-28 21:35:05 +00:00
|
|
|
}
|