1use schemars::JsonSchema;
2use serde::{Deserialize, Deserializer, Serialize};
3
4#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema)]
13#[serde(untagged)]
14pub enum StringOrBool {
15 Bool(bool),
16 String(String),
17}
18
19impl StringOrBool {
20 pub fn as_bool(&self) -> bool {
23 match self {
24 StringOrBool::Bool(b) => *b,
25 StringOrBool::String(s) => matches!(s.trim(), "true" | "1"),
26 }
27 }
28
29 pub fn as_str(&self) -> &str {
31 match self {
32 StringOrBool::Bool(true) => "true",
33 StringOrBool::Bool(false) => "false",
34 StringOrBool::String(s) => s,
35 }
36 }
37
38 pub fn is_template(&self) -> bool {
40 matches!(self, StringOrBool::String(s) if s.contains('{'))
41 }
42
43 pub fn try_evaluates_to_true(
58 &self,
59 render: impl Fn(&str) -> anyhow::Result<String>,
60 ) -> anyhow::Result<bool> {
61 match self {
62 StringOrBool::Bool(b) => Ok(*b),
63 StringOrBool::String(s) => {
64 let rendered = render(s)?;
65 Ok(matches!(rendered.trim(), "true" | "1"))
66 }
67 }
68 }
69}
70
71impl Default for StringOrBool {
72 fn default() -> Self {
73 StringOrBool::Bool(false)
74 }
75}
76
77pub(crate) fn deserialize_string_or_bool_opt<'de, D>(
79 deserializer: D,
80) -> Result<Option<StringOrBool>, D::Error>
81where
82 D: Deserializer<'de>,
83{
84 use serde::de::{self, Visitor};
85
86 struct StringOrBoolVisitor;
87
88 impl<'de> Visitor<'de> for StringOrBoolVisitor {
89 type Value = Option<StringOrBool>;
90
91 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 f.write_str("a bool, a string, or null")
93 }
94
95 fn visit_bool<E: de::Error>(self, v: bool) -> Result<Self::Value, E> {
96 Ok(Some(StringOrBool::Bool(v)))
97 }
98
99 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
100 Ok(Some(StringOrBool::String(v.to_owned())))
101 }
102
103 fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
104 Ok(Some(StringOrBool::String(v)))
105 }
106
107 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
108 Ok(None)
109 }
110
111 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
112 Ok(None)
113 }
114 }
115
116 deserializer.deserialize_any(StringOrBoolVisitor)
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, JsonSchema)]
125pub struct HumanDuration(
126 #[serde(serialize_with = "serialize_human_duration")] pub std::time::Duration,
127);
128
129impl HumanDuration {
130 pub fn duration(&self) -> std::time::Duration {
132 self.0
133 }
134
135 pub fn as_humantime_string(&self) -> String {
139 let total_secs = self.0.as_secs();
140 if total_secs == 0 {
141 return format!("{}ms", self.0.as_millis());
143 }
144 let hours = total_secs / 3600;
145 let mins = (total_secs % 3600) / 60;
146 let secs = total_secs % 60;
147 let mut out = String::new();
148 if hours > 0 {
149 out.push_str(&format!("{hours}h"));
150 }
151 if mins > 0 {
152 out.push_str(&format!("{mins}m"));
153 }
154 if secs > 0 || out.is_empty() {
155 out.push_str(&format!("{secs}s"));
156 }
157 out
158 }
159}
160
161impl<'de> Deserialize<'de> for HumanDuration {
162 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
163 where
164 D: Deserializer<'de>,
165 {
166 use serde::de::{self, Visitor};
167
168 struct DurVisitor;
169
170 impl<'de> Visitor<'de> for DurVisitor {
171 type Value = HumanDuration;
172
173 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 f.write_str(
175 "a duration string with unit suffix (e.g. \"10m\", \"15s\", \"1h30m\", \"500ms\")",
176 )
177 }
178
179 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
180 parse_humantime_duration(v)
181 .map(HumanDuration)
182 .map_err(E::custom)
183 }
184
185 fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
186 self.visit_str(&v)
187 }
188 }
189
190 deserializer.deserialize_str(DurVisitor)
191 }
192}
193
194fn serialize_human_duration<S: serde::Serializer>(
195 d: &std::time::Duration,
196 serializer: S,
197) -> Result<S::Ok, S::Error> {
198 serializer.serialize_str(&HumanDuration(*d).as_humantime_string())
199}
200
201pub(super) fn parse_humantime_duration(input: &str) -> Result<std::time::Duration, String> {
205 let s = input.trim();
206 if s.is_empty() {
207 return Err("empty duration string".to_string());
208 }
209 let mut total = std::time::Duration::ZERO;
210 let mut number_buf = String::new();
211 let mut had_any = false;
212 let mut iter = s.chars().peekable();
213 while let Some(&c) = iter.peek() {
214 if c.is_whitespace() {
215 iter.next();
216 continue;
217 }
218 if c.is_ascii_digit() {
219 number_buf.push(c);
220 iter.next();
221 continue;
222 }
223 if number_buf.is_empty() {
224 return Err(format!("expected digit before unit in '{input}'"));
225 }
226 let mut unit = String::new();
228 unit.push(c);
229 iter.next();
230 if let Some(&next) = iter.peek()
231 && unit == "m"
232 && next == 's'
233 {
234 unit.push('s');
235 iter.next();
236 }
237 let n: u64 = number_buf
238 .parse()
239 .map_err(|e| format!("invalid number '{number_buf}' in '{input}': {e}"))?;
240 let segment = match unit.as_str() {
241 "ms" => std::time::Duration::from_millis(n),
242 "s" => std::time::Duration::from_secs(n),
243 "m" => std::time::Duration::from_secs(n * 60),
244 "h" => std::time::Duration::from_secs(n * 3600),
245 "d" => std::time::Duration::from_secs(n * 86_400),
246 other => return Err(format!("unknown duration unit '{other}' in '{input}'")),
247 };
248 total += segment;
249 number_buf.clear();
250 had_any = true;
251 }
252 if !number_buf.is_empty() {
253 return Err(format!(
254 "trailing number '{number_buf}' without a unit in '{input}'"
255 ));
256 }
257 if !had_any {
258 return Err(format!("no duration components found in '{input}'"));
259 }
260 Ok(total)
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
271#[serde(transparent)]
272pub struct StringOrU32(#[serde(deserialize_with = "deserialize_u32_from_string_or_int")] pub u32);
273
274impl StringOrU32 {
275 pub fn value(&self) -> u32 {
277 self.0
278 }
279}
280
281fn deserialize_u32_from_string_or_int<'de, D>(deserializer: D) -> Result<u32, D::Error>
283where
284 D: Deserializer<'de>,
285{
286 use serde::de::{self, Visitor};
287
288 struct U32Visitor;
289
290 impl<'de> Visitor<'de> for U32Visitor {
291 type Value = u32;
292
293 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294 f.write_str("a u32 integer or a string parseable as octal/decimal (e.g. 18, \"0o022\", \"022\")")
295 }
296
297 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
298 u32::try_from(v).map_err(|_| E::custom(format!("value {v} does not fit in u32")))
299 }
300
301 fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
302 u32::try_from(v).map_err(|_| E::custom(format!("value {v} does not fit in u32")))
303 }
304
305 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
306 let trimmed = v.trim();
307 if let Some(rest) = trimmed
308 .strip_prefix("0o")
309 .or_else(|| trimmed.strip_prefix("0O"))
310 {
311 return u32::from_str_radix(rest, 8)
312 .map_err(|e| E::custom(format!("invalid octal '{v}': {e}")));
313 }
314 if trimmed.starts_with('0') && trimmed.len() > 1 {
317 return u32::from_str_radix(trimmed, 8)
318 .map_err(|e| E::custom(format!("invalid octal '{v}': {e}")));
319 }
320 trimmed
321 .parse::<u32>()
322 .map_err(|e| E::custom(format!("invalid u32 '{v}': {e}")))
323 }
324
325 fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
326 self.visit_str(&v)
327 }
328 }
329
330 deserializer.deserialize_any(U32Visitor)
331}
332
333pub(super) fn deserialize_string_or_vec_opt<'de, D>(
336 deserializer: D,
337) -> Result<Option<Vec<String>>, D::Error>
338where
339 D: Deserializer<'de>,
340{
341 use serde::de::{self, Visitor};
342
343 struct StringOrVecVisitor;
344
345 impl<'de> Visitor<'de> for StringOrVecVisitor {
346 type Value = Option<Vec<String>>;
347
348 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
349 f.write_str("a string, a list of strings, or null")
350 }
351
352 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
353 Ok(Some(vec![v.to_owned()]))
354 }
355
356 fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
357 Ok(Some(vec![v]))
358 }
359
360 fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
361 let mut items = Vec::new();
362 while let Some(item) = seq.next_element::<String>()? {
363 items.push(item);
364 }
365 Ok(Some(items))
366 }
367
368 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
369 Ok(None)
370 }
371
372 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
373 Ok(None)
374 }
375 }
376
377 deserializer.deserialize_any(StringOrVecVisitor)
378}