tests and time support
This commit is contained in:
parent
b22616e209
commit
8e5d472018
@ -6,16 +6,20 @@ use log::warn;
|
||||
use serde::{self, Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use tokio::{fs, io::AsyncReadExt};
|
||||
|
||||
use crate::PROGRAM_NAME;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
pub zone_id: Box<str>,
|
||||
pub api_key: Box<str>,
|
||||
pub zone_id: String,
|
||||
pub api_key: String,
|
||||
pub domains: Vec<Box<str>>,
|
||||
#[serde(default)]
|
||||
pub max_errors_in_row: Option<usize>,
|
||||
#[serde(with = "duration_format", default)]
|
||||
pub max_duration: Option<Duration>,
|
||||
}
|
||||
|
||||
pub async fn get_config_path() -> Result<PathBuf, Vec<PathBuf>> {
|
||||
@ -67,3 +71,88 @@ pub async fn read_config<P: AsRef<Path>>(path: &P) -> anyhow::Result<Config> {
|
||||
file.read_to_string(&mut buf).await?;
|
||||
Ok(toml::from_str(&buf)?)
|
||||
}
|
||||
|
||||
mod duration_format {
|
||||
use super::*;
|
||||
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn serialize<S>(duration: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let duration = if let Some(aux) = duration.as_ref() {
|
||||
aux
|
||||
} else {
|
||||
return serializer.serialize_none();
|
||||
};
|
||||
|
||||
let mut secs = duration.as_secs();
|
||||
let nanos = duration.subsec_nanos();
|
||||
|
||||
let days = secs / 86400;
|
||||
secs %= 86400;
|
||||
let hours = secs / 3600;
|
||||
secs %= 3600;
|
||||
let minutes = secs / 60;
|
||||
secs %= 60;
|
||||
|
||||
let mut formatted = String::new();
|
||||
if days > 0 {
|
||||
formatted.push_str(&format!("{}d ", days));
|
||||
}
|
||||
if hours > 0 {
|
||||
formatted.push_str(&format!("{}h ", hours));
|
||||
}
|
||||
if minutes > 0 {
|
||||
formatted.push_str(&format!("{}m ", minutes));
|
||||
}
|
||||
if secs > 0 {
|
||||
formatted.push_str(&format!("{}s ", secs));
|
||||
}
|
||||
if nanos > 0 {
|
||||
formatted.push_str(&format!("{}ns", nanos));
|
||||
}
|
||||
|
||||
serializer.serialize_str(formatted.trim())
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Option::<Box<str>>::deserialize(deserializer)?.map_or(Ok(None), |s| {
|
||||
parse_duration(&s)
|
||||
.map(Some)
|
||||
.map_err(serde::de::Error::custom)
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_duration(s: &str) -> Result<Duration, String> {
|
||||
let mut total_duration = Duration::new(0, 0);
|
||||
let units = [("d", 86400), ("h", 3600), ("m", 60), ("s", 1)];
|
||||
let mut remainder = s;
|
||||
|
||||
for &(unit, factor) in &units {
|
||||
if let Some(idx) = remainder.find(unit) {
|
||||
let (value, rest) = remainder.split_at(idx);
|
||||
let value: u64 = value.trim().parse().map_err(|_| "Invalid number")?;
|
||||
total_duration += Duration::from_secs(value * factor);
|
||||
remainder = &rest[unit.len()..];
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(idx) = remainder.find("ns") {
|
||||
let (value, rest) = remainder.split_at(idx);
|
||||
let value: u32 = value.trim().parse().map_err(|_| "Invalid number")?;
|
||||
total_duration += Duration::new(0, value);
|
||||
remainder = &rest["ns".len()..];
|
||||
}
|
||||
|
||||
if !remainder.trim().is_empty() {
|
||||
return Err("Invalid duration format".to_string());
|
||||
}
|
||||
|
||||
Ok(total_duration)
|
||||
}
|
||||
}
|
||||
|
@ -3,15 +3,16 @@
|
||||
mod cloudflare;
|
||||
mod config;
|
||||
mod logging;
|
||||
mod public_ip;
|
||||
mod message_handler;
|
||||
mod public_ip;
|
||||
mod tests;
|
||||
pub mod utils;
|
||||
|
||||
pub use cloudflare::CloudflareClient;
|
||||
pub use config::{get_config_path, read_config, Config};
|
||||
pub use logging::init_logger;
|
||||
pub use public_ip::get_current_public_ipv4;
|
||||
pub use message_handler::MessageHandler;
|
||||
pub use public_ip::get_current_public_ipv4;
|
||||
|
||||
pub const PROGRAM_NAME: &'static str = "dynip-cloudflare";
|
||||
pub const MAX_ERORS_IN_ROW_DEFAULT: usize = 10;
|
||||
|
75
src/tests/config_serialization.rs
Normal file
75
src/tests/config_serialization.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use std::time::Duration;
|
||||
use toml;
|
||||
use crate::Config;
|
||||
|
||||
const TOML_STR_ONE: &str = r#"
|
||||
zone_id = ""
|
||||
api_key = ""
|
||||
domains = [""]
|
||||
max_duration = "1d 2h 30m 45s 500000000ns"
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn test_deserialize() {
|
||||
let config: Config = toml::from_str(TOML_STR_ONE).unwrap();
|
||||
assert_eq!(config.max_duration, Some(Duration::new(95445, 500000000)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize() {
|
||||
let config = Config {
|
||||
zone_id: "".into(),
|
||||
api_key: "".into(),
|
||||
domains: vec!["".into()],
|
||||
max_errors_in_row: None,
|
||||
max_duration: Some(Duration::new(95445, 500000000)),
|
||||
};
|
||||
let toml_str = toml::to_string(&config).unwrap();
|
||||
assert_eq!(TOML_STR_ONE.trim(), toml_str.trim());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_none() {
|
||||
let toml_str = r#"
|
||||
zone_id = ""
|
||||
api_key = ""
|
||||
domains = [""]
|
||||
max_errors_in_row = 5
|
||||
"#;
|
||||
let config: Config = toml::from_str(toml_str).unwrap();
|
||||
assert_eq!(config.max_duration, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_none() {
|
||||
let toml_to_be = r#"
|
||||
zone_id = ""
|
||||
api_key = ""
|
||||
domains = [""]
|
||||
"#;
|
||||
let config = Config {
|
||||
zone_id: "".into(),
|
||||
api_key: "".into(),
|
||||
domains: vec!["".into()],
|
||||
max_errors_in_row: None,
|
||||
max_duration: None,
|
||||
};
|
||||
let toml_str = toml::to_string(&config).unwrap();
|
||||
assert_eq!(toml_to_be.trim(), toml_str.trim());
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let toml_str = r#"
|
||||
zone_id = ""
|
||||
api_key = ""
|
||||
domains = [""]
|
||||
max_errors_in_row = 5
|
||||
max_duration = "1d 2h 30m 45s 500000000ns"
|
||||
"#;
|
||||
|
||||
let config: Config = toml::from_str(toml_str).unwrap();
|
||||
println!("{:?}", config);
|
||||
|
||||
let toml_out = toml::to_string(&config).unwrap();
|
||||
println!("{}", toml_out);
|
||||
}
|
2
src/tests/mod.rs
Normal file
2
src/tests/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
#[cfg(test)]
|
||||
mod config_serialization;
|
Loading…
x
Reference in New Issue
Block a user