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