1use std::fmt::{Debug, Display, Formatter};
18use std::str::FromStr;
19use std::time::Duration;
20
21use humantime::{format_duration, parse_duration};
22use reqwest::header::HeaderValue;
23
24use crate::{Error, Result};
25
26#[derive(Debug, Clone)]
30pub enum ConfigValue<T> {
31 Parsed(T),
32 Deferred(String),
33}
34
35impl<T: Display> Display for ConfigValue<T> {
36 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
37 match self {
38 Self::Parsed(v) => write!(f, "{v}"),
39 Self::Deferred(v) => write!(f, "{v}"),
40 }
41 }
42}
43
44impl<T> From<T> for ConfigValue<T> {
45 fn from(value: T) -> Self {
46 Self::Parsed(value)
47 }
48}
49
50impl<T: Parse + Clone> ConfigValue<T> {
51 pub fn parse(&mut self, v: impl Into<String>) {
52 *self = Self::Deferred(v.into())
53 }
54
55 pub fn get(&self) -> Result<T> {
56 match self {
57 Self::Parsed(v) => Ok(v.clone()),
58 Self::Deferred(v) => T::parse(v),
59 }
60 }
61}
62
63impl<T: Default> Default for ConfigValue<T> {
64 fn default() -> Self {
65 Self::Parsed(T::default())
66 }
67}
68
69pub trait Parse: Sized {
71 fn parse(v: &str) -> Result<Self>;
72}
73
74impl Parse for bool {
75 fn parse(v: &str) -> Result<Self> {
76 let lower = v.to_ascii_lowercase();
77 match lower.as_str() {
78 "1" | "true" | "on" | "yes" | "y" => Ok(true),
79 "0" | "false" | "off" | "no" | "n" => Ok(false),
80 _ => Err(Error::Generic {
81 store: "Config",
82 source: format!("failed to parse \"{v}\" as boolean").into(),
83 }),
84 }
85 }
86}
87
88impl Parse for Duration {
89 fn parse(v: &str) -> Result<Self> {
90 parse_duration(v).map_err(|_| Error::Generic {
91 store: "Config",
92 source: format!("failed to parse \"{v}\" as Duration").into(),
93 })
94 }
95}
96
97impl Parse for usize {
98 fn parse(v: &str) -> Result<Self> {
99 Self::from_str(v).map_err(|_| Error::Generic {
100 store: "Config",
101 source: format!("failed to parse \"{v}\" as usize").into(),
102 })
103 }
104}
105
106impl Parse for HeaderValue {
107 fn parse(v: &str) -> Result<Self> {
108 Self::from_str(v).map_err(|_| Error::Generic {
109 store: "Config",
110 source: format!("failed to parse \"{v}\" as HeaderValue").into(),
111 })
112 }
113}
114
115pub(crate) fn fmt_duration(duration: &ConfigValue<Duration>) -> String {
116 match duration {
117 ConfigValue::Parsed(v) => format_duration(*v).to_string(),
118 ConfigValue::Deferred(v) => v.clone(),
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use std::time::Duration;
126
127 #[test]
128 fn test_parse_duration() {
129 let duration = Duration::from_secs(60);
130 assert_eq!(Duration::parse("60 seconds").unwrap(), duration);
131 assert_eq!(Duration::parse("60 s").unwrap(), duration);
132 assert_eq!(Duration::parse("60s").unwrap(), duration)
133 }
134}