use std::fmt::Formatter;
use serde::{Deserialize, Serialize, de::Error};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize)]
pub struct StickyDurationMs(u32);
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct StickyObject {
pub duration_ms: StickyDurationMs,
}
impl StickyDurationMs {
pub const MAX: u32 = 3_600_000;
pub fn new_clamped<T: Into<u64>>(v: T) -> Self {
let v = v.into();
let clamped = v.min(Self::MAX as u64) as u32;
Self(clamped)
}
pub fn get(self) -> u32 {
self.into()
}
}
impl From<StickyDurationMs> for u32 {
fn from(d: StickyDurationMs) -> Self {
d.0
}
}
impl<'de> Deserialize<'de> for StickyDurationMs {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct StickyDurationMsVisitor;
impl<'de> serde::de::Visitor<'de> for StickyDurationMsVisitor {
type Value = StickyDurationMs;
fn expecting(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "an integer in the range 0-3600000")
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: Error,
{
if v < 0 {
Err(E::invalid_value(serde::de::Unexpected::Signed(v), &self))
} else {
self.visit_u64(v as u64)
}
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: Error,
{
if v > StickyDurationMs::MAX as u64 {
Err(E::invalid_value(serde::de::Unexpected::Unsigned(v), &self))
} else {
Ok(StickyDurationMs(v as u32))
}
}
}
deserializer.deserialize_u32(StickyDurationMsVisitor)
}
}
#[cfg(test)]
mod tests {
use crate::sticky::{StickyDurationMs, StickyObject};
#[test]
fn deserialize_sticky_ok() {
let raw = "78000";
let sticky_duration: StickyDurationMs = serde_json::from_str(raw).unwrap();
assert_eq!(sticky_duration.get(), 78_000_u32);
}
#[test]
fn serialize_sticky() {
let sticky = StickyDurationMs::new_clamped(60_000_u32);
let ser = serde_json::to_string(&sticky).unwrap();
assert_eq!(ser, "60000");
}
#[test]
fn deserialize_sticky_oob() {
let raw = "3600001";
let res: Result<StickyDurationMs, _> = serde_json::from_str(raw);
assert!(res.is_err());
let err = res.unwrap_err();
let err_msg = err.to_string();
assert!(err_msg.contains(
"invalid value: integer `3600001`, expected an integer in the range 0-3600000"
));
}
#[test]
fn deserialize_sticky_float_err() {
let raw = "3000.0";
let res: Result<StickyDurationMs, _> = serde_json::from_str(raw);
assert!(res.is_err());
let err = res.unwrap_err();
let err_msg = err.to_string();
assert!(err_msg.contains(
"invalid type: floating point `3000.0`, expected an integer in the range 0-3600000"
));
}
#[test]
fn deserialize_sticky_string_err() {
let raw = r#""3000""#;
let res: Result<StickyDurationMs, _> = serde_json::from_str(raw);
assert!(res.is_err());
let err = res.unwrap_err();
assert!(
err.to_string().contains(
"invalid type: string \"3000\", expected an integer in the range 0-3600000"
)
);
}
#[test]
fn deserialize_sticky_neg_err() {
let raw = "-1";
let res: Result<StickyDurationMs, _> = serde_json::from_str(raw);
assert!(res.is_err());
let err = res.unwrap_err();
let err_msg = err.to_string();
println!("{}", err_msg);
assert!(
err_msg.contains(
"invalid value: integer `-1`, expected an integer in the range 0-3600000"
)
);
}
#[test]
fn deserialize_sticky_object_ok() {
let ser = r#"{"duration_ms":78000}"#;
let sticky_object: StickyObject = serde_json::from_str(ser).unwrap();
assert_eq!(sticky_object.duration_ms.get(), 78_000_u32);
}
#[test]
fn serialize_sticky_object() {
let sticky_object = StickyObject { duration_ms: StickyDurationMs::new_clamped(78_000_u32) };
let ser = serde_json::to_string(&sticky_object).unwrap();
assert_eq!(ser, r#"{"duration_ms":78000}"#);
}
#[test]
fn deserialize_sticky_object_err() {
let raw = r#"{"duration_ms":3600001}"#;
let res: Result<StickyObject, _> = serde_json::from_str(raw);
assert!(res.is_err());
}
}