use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
use std::time::Duration;
use humantime::{format_duration, parse_duration};
use reqwest::header::HeaderValue;
use crate::{Error, Result};
#[derive(Debug, Clone)]
pub enum ConfigValue<T> {
Parsed(T),
Deferred(String),
}
impl<T: Display> Display for ConfigValue<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Parsed(v) => write!(f, "{v}"),
Self::Deferred(v) => write!(f, "{v}"),
}
}
}
impl<T> From<T> for ConfigValue<T> {
fn from(value: T) -> Self {
Self::Parsed(value)
}
}
impl<T: Parse + Clone> ConfigValue<T> {
pub fn parse(&mut self, v: impl Into<String>) {
*self = Self::Deferred(v.into())
}
pub fn get(&self) -> Result<T> {
match self {
Self::Parsed(v) => Ok(v.clone()),
Self::Deferred(v) => T::parse(v),
}
}
}
impl<T: Default> Default for ConfigValue<T> {
fn default() -> Self {
Self::Parsed(T::default())
}
}
pub trait Parse: Sized {
fn parse(v: &str) -> Result<Self>;
}
impl Parse for bool {
fn parse(v: &str) -> Result<Self> {
let lower = v.to_ascii_lowercase();
match lower.as_str() {
"1" | "true" | "on" | "yes" | "y" => Ok(true),
"0" | "false" | "off" | "no" | "n" => Ok(false),
_ => Err(Error::Generic {
store: "Config",
source: format!("failed to parse \"{v}\" as boolean").into(),
}),
}
}
}
impl Parse for Duration {
fn parse(v: &str) -> Result<Self> {
parse_duration(v).map_err(|_| Error::Generic {
store: "Config",
source: format!("failed to parse \"{v}\" as Duration").into(),
})
}
}
impl Parse for usize {
fn parse(v: &str) -> Result<Self> {
Self::from_str(v).map_err(|_| Error::Generic {
store: "Config",
source: format!("failed to parse \"{v}\" as usize").into(),
})
}
}
impl Parse for HeaderValue {
fn parse(v: &str) -> Result<Self> {
Self::from_str(v).map_err(|_| Error::Generic {
store: "Config",
source: format!("failed to parse \"{v}\" as HeaderValue").into(),
})
}
}
pub(crate) fn fmt_duration(duration: &ConfigValue<Duration>) -> String {
match duration {
ConfigValue::Parsed(v) => format_duration(*v).to_string(),
ConfigValue::Deferred(v) => v.clone(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_parse_duration() {
let duration = Duration::from_secs(60);
assert_eq!(Duration::parse("60 seconds").unwrap(), duration);
assert_eq!(Duration::parse("60 s").unwrap(), duration);
assert_eq!(Duration::parse("60s").unwrap(), duration)
}
}