use serde::Deserialize;
use std::path::Path;
const DEFAULT_CONFIG_FILE: &str = "velesdb.toml";
#[derive(Debug, Clone, Deserialize)]
pub struct UpdateCheckConfig {
#[serde(default = "default_enabled")]
pub enabled: bool,
#[serde(default = "default_endpoint")]
pub endpoint: String,
#[serde(default = "default_timeout_ms")]
pub timeout_ms: u64,
}
fn default_enabled() -> bool {
true
}
fn default_endpoint() -> String {
"https://velesdb.com/api/check".to_string()
}
fn default_timeout_ms() -> u64 {
2000
}
impl Default for UpdateCheckConfig {
fn default() -> Self {
Self {
enabled: default_enabled(),
endpoint: default_endpoint(),
timeout_ms: default_timeout_ms(),
}
}
}
impl UpdateCheckConfig {
#[must_use]
pub fn from_config_file(path: Option<&Path>) -> Self {
match path {
Some(explicit) => Self::load(explicit, true),
None => Self::load(Path::new(DEFAULT_CONFIG_FILE), false),
}
}
fn load(path: &Path, explicit: bool) -> Self {
match std::fs::read_to_string(path) {
Ok(document) => Self::from_toml_str(&document),
Err(e) if e.kind() == std::io::ErrorKind::NotFound && !explicit => Self::default(),
Err(error) => Self::fail_closed(&error),
}
}
fn from_toml_str(document: &str) -> Self {
let root = match toml::from_str::<toml::Table>(document) {
Ok(root) => root,
Err(error) => return Self::fail_closed(&error),
};
match root.get("update_check") {
None => Self::default(),
Some(table) => table
.clone()
.try_into()
.unwrap_or_else(|error| Self::fail_closed(&error)),
}
}
fn fail_closed(error: &dyn std::fmt::Display) -> Self {
tracing::warn!(
%error,
"the configuration file's [update_check] cannot be read, so the update check is disabled until it is fixed"
);
Self {
enabled: false,
..Self::default()
}
}
#[must_use]
pub fn is_enabled(&self) -> bool {
self.is_enabled_with(|key| std::env::var(key).ok())
}
pub(super) fn is_enabled_with(&self, lookup: impl Fn(&str) -> Option<String>) -> bool {
if lookup("VELESDB_NO_UPDATE_CHECK").is_some_and(|val| is_truthy(&val)) {
return false;
}
if let Some(val) = lookup("VELESDB_UPDATE_CHECK") {
return is_truthy(&val);
}
self.enabled
}
}
pub(super) fn is_falsy(val: &str) -> bool {
matches!(val.to_lowercase().as_str(), "0" | "false" | "no" | "off")
}
fn is_truthy(val: &str) -> bool {
!is_falsy(val)
}
#[cfg(test)]
#[path = "config_tests.rs"]
mod tests;