use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};
use chrono::{DateTime, TimeZone, Utc};
use serde::de::{self, Deserialize, Deserializer};
pub fn from_str_opt<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
T::Err: std::fmt::Display,
{
let opt = Option::<String>::deserialize(deserializer)?;
match opt {
Some(s) => {
let parsed = s.parse().map_err(de::Error::custom)?;
Ok(Some(parsed))
}
None => Ok(None),
}
}
pub fn from_str<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
T::Err: std::fmt::Display,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(de::Error::custom)
}
pub fn mask_api_key(api_key: &str) -> String {
api_key
.chars()
.enumerate()
.map(|(i, c)| if i < 3 { c } else { '*' })
.collect()
}
pub fn timestamp_now() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or_else(|_| 0)
}
pub fn timestamp_to_datetime(timestamp: i64) -> DateTime<Utc> {
Utc.timestamp_opt(timestamp, 0)
.single()
.unwrap_or_else(|| Utc.timestamp_opt(0, 0).single().unwrap())
}
pub fn format_datetime_iso(dt: DateTime<Utc>) -> String {
dt.to_rfc3339()
}
pub fn format_duration_human(seconds: u64) -> String {
let hours = seconds / 3600;
let minutes = (seconds % 3600) / 60;
let secs = seconds % 60;
let mut parts = Vec::new();
if hours > 0 {
parts.push(format!("{}h", hours));
}
if minutes > 0 {
parts.push(format!("{}m", minutes));
}
if secs > 0 || parts.is_empty() {
parts.push(format!("{}s", secs));
}
parts.join(" ")
}
pub fn percent(value: u64, total: u64) -> Option<f64> {
if total == 0 {
None
} else {
Some((value as f64 / total as f64) * 100.0)
}
}