Skip to main content

ruma_events/
sticky.rs

1//! Types for the sticky events event defined in [MSC4354].
2//!
3//! [MSC4354]: https://github.com/matrix-org/matrix-spec-proposals/pull/4354
4
5use std::fmt::Formatter;
6
7use serde::{Deserialize, Serialize, de::Error};
8
9/// Sticky duration in milliseconds.
10///
11/// Valid values are the integer range 0-3600000 (1 hour).
12#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize)]
13pub struct StickyDurationMs(u32);
14
15/// New event top-level sticky object.
16#[derive(Clone, Debug, Serialize, Deserialize)]
17#[non_exhaustive]
18pub struct StickyObject {
19    /// The number of milliseconds for the event to be sticky.
20    pub duration_ms: StickyDurationMs,
21}
22
23impl StickyDurationMs {
24    /// The maximum possible sticky duration in millis (1 hour).
25    pub const MAX: u32 = 3_600_000;
26
27    /// Creates a `StickyDurationMs` by clamping `v` into `[0, 1h]`.
28    pub fn new_clamped<T: Into<u64>>(v: T) -> Self {
29        let v = v.into();
30        let clamped = v.min(Self::MAX as u64) as u32;
31        Self(clamped)
32    }
33
34    /// Get the value as a `u32`.
35    pub fn get(self) -> u32 {
36        self.into()
37    }
38}
39
40impl From<StickyDurationMs> for u32 {
41    fn from(d: StickyDurationMs) -> Self {
42        d.0
43    }
44}
45
46impl<'de> Deserialize<'de> for StickyDurationMs {
47    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
48    where
49        D: serde::Deserializer<'de>,
50    {
51        struct StickyDurationMsVisitor;
52
53        impl<'de> serde::de::Visitor<'de> for StickyDurationMsVisitor {
54            type Value = StickyDurationMs;
55
56            fn expecting(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
57                write!(f, "an integer in the range 0-3600000")
58            }
59
60            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
61            where
62                E: Error,
63            {
64                if v < 0 {
65                    Err(E::invalid_value(serde::de::Unexpected::Signed(v), &self))
66                } else {
67                    self.visit_u64(v as u64)
68                }
69            }
70
71            // serde json deserialize json numbers as u64 by default
72            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
73            where
74                E: Error,
75            {
76                if v > StickyDurationMs::MAX as u64 {
77                    Err(E::invalid_value(serde::de::Unexpected::Unsigned(v), &self))
78                } else {
79                    Ok(StickyDurationMs(v as u32))
80                }
81            }
82        }
83
84        deserializer.deserialize_any(StickyDurationMsVisitor)
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use crate::sticky::{StickyDurationMs, StickyObject};
91
92    #[test]
93    fn deserialize_sticky_ok() {
94        let raw = "78000";
95        let sticky_duration: StickyDurationMs = serde_json::from_str(raw).unwrap();
96        assert_eq!(sticky_duration.get(), 78_000_u32);
97    }
98
99    #[test]
100    fn serialize_sticky() {
101        let sticky = StickyDurationMs::new_clamped(60_000_u32);
102        let ser = serde_json::to_string(&sticky).unwrap();
103        assert_eq!(ser, "60000");
104    }
105
106    #[test]
107    fn deserialize_sticky_oob() {
108        let raw = "3600001";
109        let res: Result<StickyDurationMs, _> = serde_json::from_str(raw);
110        assert!(res.is_err());
111
112        let err = res.unwrap_err();
113        let err_msg = err.to_string();
114        assert!(err_msg.contains(
115            "invalid value: integer `3600001`, expected an integer in the range 0-3600000"
116        ));
117    }
118
119    #[test]
120    fn deserialize_sticky_float_err() {
121        let raw = "3000.0";
122        let res: Result<StickyDurationMs, _> = serde_json::from_str(raw);
123        assert!(res.is_err());
124
125        let err = res.unwrap_err();
126        let err_msg = err.to_string();
127        assert!(err_msg.contains(
128            "invalid type: floating point `3000.0`, expected an integer in the range 0-3600000"
129        ));
130    }
131
132    #[test]
133    fn deserialize_sticky_neg_err() {
134        let raw = "-1";
135        let res: Result<StickyDurationMs, _> = serde_json::from_str(raw);
136        assert!(res.is_err());
137
138        let err = res.unwrap_err();
139        let err_msg = err.to_string();
140        println!("{}", err_msg);
141        assert!(
142            err_msg.contains(
143                "invalid value: integer `-1`, expected an integer in the range 0-3600000"
144            )
145        );
146    }
147
148    #[test]
149    fn deserialize_sticky_object_ok() {
150        let ser = r#"{"duration_ms":78000}"#;
151        let sticky_object: StickyObject = serde_json::from_str(ser).unwrap();
152        assert_eq!(sticky_object.duration_ms.get(), 78_000_u32);
153    }
154
155    #[test]
156    fn deserialize_sticky_object_err() {
157        let raw = r#"{"duration_ms":3600001}"#;
158        let res: Result<StickyObject, _> = serde_json::from_str(raw);
159        assert!(res.is_err());
160    }
161}