architect_api/utils/
duration.rs1use crate::json_schema_is_string;
4use anyhow::{anyhow, bail, Result};
5use chrono::{DateTime, Duration, Utc};
6use derive::Newtype;
7#[cfg(feature = "netidx")]
8use netidx_derive::Pack;
9use serde::{Deserialize, Serialize};
10use serde_with::serde_conv;
11use std::str::FromStr;
12
13serde_conv!(pub DurationAsStr, Duration, format_duration, parse_duration);
14
15serde_conv!(
16 pub NonZeroDurationAsStr,
17 std::time::Duration,
18 format_nonzero_duration,
19 parse_nonzero_duration
20);
21
22fn format_nonzero_duration(dur: &std::time::Duration) -> String {
23 let secs = dur.as_secs();
24 let nanos = dur.subsec_nanos();
25 format!("{}.{:09}s", secs, nanos)
26}
27
28fn parse_nonzero_duration(s: &str) -> Result<std::time::Duration> {
29 let dur = parse_duration(s)?;
30 if dur.is_zero() {
31 bail!("duration must be non-zero");
32 }
33 Ok(dur.to_std()?)
34}
35
36json_schema_is_string!(DurationAsStr);
37json_schema_is_string!(NonZeroDurationAsStr);
38
39#[derive(
41 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Newtype,
42)]
43#[newtype(Deref, DerefMut, From)]
44#[serde(transparent)]
45#[cfg_attr(feature = "netidx", derive(Pack))]
46#[cfg_attr(feature = "netidx", pack(unwrapped))]
47pub struct HumanDuration(
48 #[serde(
49 serialize_with = "serialize_duration",
50 deserialize_with = "deserialize_duration"
51 )]
52 pub Duration,
53);
54
55impl FromStr for HumanDuration {
56 type Err = anyhow::Error;
57
58 fn from_str(s: &str) -> Result<Self> {
59 parse_duration(s).map(HumanDuration)
60 }
61}
62
63json_schema_is_string!(HumanDuration);
64
65#[derive(Debug, Clone)]
68pub enum AbsoluteOrRelativeTime {
69 Absolute(DateTime<Utc>),
70 RelativeFuture(Duration),
71 RelativePast(Duration),
72}
73
74impl AbsoluteOrRelativeTime {
75 pub fn resolve_to(&self, now: DateTime<Utc>) -> DateTime<Utc> {
76 match self {
77 Self::Absolute(dt) => *dt,
78 Self::RelativeFuture(d) => now + *d,
79 Self::RelativePast(d) => now - *d,
80 }
81 }
82
83 pub fn resolve(&self) -> DateTime<Utc> {
84 self.resolve_to(Utc::now())
85 }
86}
87
88impl FromStr for AbsoluteOrRelativeTime {
89 type Err = anyhow::Error;
90
91 fn from_str(s: &str) -> Result<Self> {
92 if s.starts_with('+') {
93 Ok(Self::RelativeFuture(parse_duration(&s[1..])?))
94 } else if s.starts_with('_') || s.starts_with("~") {
95 Ok(Self::RelativePast(parse_duration(&s[1..])?))
98 } else {
99 Ok(Self::Absolute(DateTime::from_str(s)?))
100 }
101 }
102}
103
104fn format_duration(dur: &Duration) -> String {
106 let secs = dur.num_milliseconds() as f64 / 1000.;
107 format!("{}s", secs)
108}
109
110pub fn parse_duration(s: &str) -> Result<Duration> {
125 if s.ends_with("ns") {
126 let s = s.strip_suffix("ns").unwrap().trim();
127 let n = s.parse::<i64>().map_err(|e| anyhow!(e.to_string()))?;
128 Ok(Duration::nanoseconds(n))
129 } else if s.ends_with("us") {
130 let s = s.strip_suffix("us").unwrap().trim();
131 let n = s.parse::<i64>().map_err(|e| anyhow!(e.to_string()))?;
132 Ok(Duration::microseconds(n))
133 } else if s.ends_with("ms") {
134 let s = s.strip_suffix("ms").unwrap().trim();
135 let n = s.parse::<i64>().map_err(|e| anyhow!(e.to_string()))?;
136 Ok(Duration::milliseconds(n))
137 } else if s.ends_with("s") {
138 let s = s.strip_suffix("s").unwrap().trim();
139 let f = s.parse::<f64>().map_err(|e| anyhow!(e.to_string()))?;
140 Ok(Duration::nanoseconds((f * 1e9).trunc() as i64))
141 } else if s.ends_with("m") {
142 let s = s.strip_suffix("m").unwrap().trim();
143 let f = s.parse::<f64>().map_err(|e| anyhow!(e.to_string()))?;
144 Ok(Duration::nanoseconds((f * 60. * 1e9).trunc() as i64))
145 } else if s.ends_with("h") {
146 let s = s.strip_suffix("h").unwrap().trim();
147 let f = s.parse::<f64>().map_err(|e| anyhow!(e.to_string()))?;
148 Ok(Duration::nanoseconds((f * 3600. * 1e9).trunc() as i64))
149 } else if s.ends_with("d") {
150 let s = s.strip_suffix("d").unwrap().trim();
151 let f = s.parse::<f64>().map_err(|e| anyhow!(e.to_string()))?;
152 Ok(Duration::nanoseconds((f * 86400. * 1e9).trunc() as i64))
153 } else {
154 Err(anyhow!("expected a suffix ns, us, ms, s, m, h, d"))
155 }
156}
157
158pub struct DurationVisitor;
160
161impl<'de> serde::de::Visitor<'de> for DurationVisitor {
162 type Value = Duration;
163
164 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
165 write!(f, "expecting a string")
166 }
167
168 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
169 where
170 E: serde::de::Error,
171 {
172 parse_duration(s).map_err(|e| E::custom(e.to_string()))
173 }
174}
175
176pub fn deserialize_duration<'de, D>(d: D) -> Result<Duration, D::Error>
180where
181 D: serde::Deserializer<'de>,
182{
183 d.deserialize_str(DurationVisitor)
184}
185
186pub fn deserialize_duration_opt<'de, D>(d: D) -> Result<Option<Duration>, D::Error>
187where
188 D: serde::Deserializer<'de>,
189{
190 let s = Option::<String>::deserialize(d)?;
191 match s {
192 Some(s) => Ok(Some(parse_duration(&s).map_err(serde::de::Error::custom)?)),
193 None => Ok(None),
194 }
195}
196
197pub fn serialize_duration<S>(d: &Duration, s: S) -> Result<S::Ok, S::Error>
202where
203 S: serde::Serializer,
204{
205 let secs = d.num_milliseconds() as f64 / 1000.;
206 s.serialize_str(&format!("{}s", secs))
207}
208
209pub fn serialize_duration_opt<S>(d: &Option<Duration>, s: S) -> Result<S::Ok, S::Error>
210where
211 S: serde::Serializer,
212{
213 match d {
214 Some(d) => {
215 let secs = d.num_milliseconds() as f64 / 1000.;
216 s.serialize_some(&format!("{}s", secs))
217 }
218 None => s.serialize_none(),
219 }
220}