use std::time::Duration;
use serde::de::Error;
use serde::{
Deserialize,
Deserializer,
Serializer,
};
use super::duration_millis::as_millis_u64;
pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format(duration))
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
match value {
serde_json::Value::Number(number) => {
let millis = number
.as_u64()
.ok_or_else(|| D::Error::custom("duration integer must be a non-negative u64"))?;
Ok(Duration::from_millis(millis))
}
serde_json::Value::String(text) => parse(&text).map_err(D::Error::custom),
_ => Err(D::Error::custom(
"duration must be a string with unit or a millisecond integer",
)),
}
}
#[inline]
pub fn format(duration: &Duration) -> String {
format!("{}ms", as_millis_u64(duration))
}
pub fn parse(text: &str) -> Result<Duration, String> {
let trimmed = text.trim();
if trimmed.is_empty() {
return Err("duration must not be empty".to_string());
}
if let Ok(millis) = trimmed.parse::<u64>() {
return Ok(Duration::from_millis(millis));
}
let (number, unit) = split_number_and_unit(trimmed)?;
let value = number.parse::<u64>().map_err(|_| {
format!("invalid duration value `{number}`: expected a non-negative integer")
})?;
duration_from_unit(value, unit)
}
fn split_number_and_unit(text: &str) -> Result<(&str, &str), String> {
let split_at = text
.find(|ch: char| !ch.is_ascii_digit())
.ok_or_else(|| "duration unit is missing".to_string())?;
let (number, unit) = text.split_at(split_at);
if number.is_empty() {
return Err("duration value is missing".to_string());
}
Ok((number, unit))
}
fn duration_from_unit(value: u64, unit: &str) -> Result<Duration, String> {
match unit {
"ns" => Ok(Duration::from_nanos(value)),
"us" | "µs" => Ok(Duration::from_micros(value)),
"ms" => Ok(Duration::from_millis(value)),
"s" => Ok(Duration::from_secs(value)),
"m" => value
.checked_mul(60)
.map(Duration::from_secs)
.ok_or_else(|| "duration minutes overflow u64 seconds".to_string()),
"h" => value
.checked_mul(60 * 60)
.map(Duration::from_secs)
.ok_or_else(|| "duration hours overflow u64 seconds".to_string()),
"d" => value
.checked_mul(24 * 60 * 60)
.map(Duration::from_secs)
.ok_or_else(|| "duration days overflow u64 seconds".to_string()),
_ => Err(format!("unsupported duration unit `{unit}`")),
}
}