Skip to main content

ruma_client_api/retention/
get_retention_configuration.rs

1//! `GET /_matrix/client/*/retention/configuration`
2//!
3//! Get the configuration for the message retention policy.
4
5pub mod unstable {
6    //! `msc1763` ([MSC])
7    //!
8    //! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/1763
9
10    use std::{collections::BTreeMap, ops::RangeBounds, time::Duration};
11
12    use js_int::UInt;
13    use ruma_common::{
14        api::{auth_scheme::AccessToken, request, response},
15        metadata,
16    };
17    use ruma_events::room::retention::{RoomRetentionEventContent, is_valid_lifetime_combination};
18    use serde::{Deserialize, Serialize};
19
20    use crate::retention::RoomIdOrAllRooms;
21
22    metadata! {
23        method: GET,
24        rate_limited: false,
25        authentication: AccessToken,
26        history: {
27            unstable => "/_matrix/client/unstable/org.matrix.msc1763/retention/configuration",
28        }
29    }
30
31    /// Request type for the `GET` `retention/configuration` endpoint.
32    #[request]
33    #[derive(Default)]
34    pub struct Request {}
35
36    impl Request {
37        /// Creates an empty `Request`.
38        pub fn new() -> Self {
39            Self {}
40        }
41    }
42
43    /// Response type for the `GET` `retention/configuration` endpoint.
44    #[response]
45    pub struct Response {
46        /// Map between a Room ID and their respective room retention policy.
47        pub policies: BTreeMap<RoomIdOrAllRooms, RoomRetentionEventContent>,
48
49        /// Limits to apply to policies defined by m.room.retention state events.
50        pub limits: RetentionLimits,
51    }
52
53    impl Response {
54        /// Creates a new `Response` with the given policies and limits.
55        pub fn new(
56            policies: BTreeMap<RoomIdOrAllRooms, RoomRetentionEventContent>,
57            limits: RetentionLimits,
58        ) -> Self {
59            Self { policies, limits }
60        }
61    }
62
63    /// Struct describing limits to apply to policies defined by `m.room.retention` state events.
64    #[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
65    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
66    pub struct RetentionLimits {
67        /// Limits to apply to the maximum lifetime of `m.room.retention` limits.
68        #[serde(skip_serializing_if = "Option::is_none")]
69        pub max_lifetime: Option<LifetimeLimits>,
70
71        /// Limits to apply to the minimum lifetime of `m.room.retention` limits.
72        #[serde(skip_serializing_if = "Option::is_none")]
73        pub min_lifetime: Option<LifetimeLimits>,
74    }
75
76    impl RetentionLimits {
77        /// Create a new [`RetentionLimits`] object with the given maximum and minimum limits.
78        pub fn new(
79            min_lifetime: Option<LifetimeLimits>,
80            max_lifetime: Option<LifetimeLimits>,
81        ) -> Self {
82            Self { min_lifetime, max_lifetime }
83        }
84    }
85
86    /// Global limits for the per-room retention policy lifetimes.
87    #[derive(Clone, Copy, Debug, Default, Serialize)]
88    pub struct LifetimeLimits {
89        /// The minimum accepted value for this limit.
90        min: Option<UInt>,
91
92        /// The maximum accepted value for this limit.
93        max: Option<UInt>,
94    }
95
96    impl LifetimeLimits {
97        /// Create a new [`LifetimeLimits`] object with no limits set.
98        ///
99        /// This method can be combined with the [`LifetimeLimits::at_least`] and
100        /// [`LifetimeLimits::at_most`] methods to configure the individual limits.
101        ///
102        /// # Examples
103        ///
104        /// ```
105        /// # use std::time::Duration;
106        /// # use ruma_client_api::retention::get_retention_configuration::unstable::LifetimeLimits;
107        /// # fn doctest() -> Option<()> {
108        /// let content = LifetimeLimits::new()
109        ///     .at_least(Duration::from_hours(24))?
110        ///     .at_most(Duration::from_hours(24 * 10))?;
111        /// # None
112        /// # }
113        /// ```
114        pub fn new() -> Self {
115            Self::default()
116        }
117
118        /// Create a new [`LifetimeLimits`] object with the given maximum and minimum limits.
119        ///
120        /// This will return `None` if the duration of one of the limits, expressed as
121        /// milliseconds, doesn't fall into the [0, (2^53)-1] range.
122        fn new_impl(min: Option<Duration>, max: Option<Duration>) -> Option<Self> {
123            let max = max.map(|l| UInt::try_from(l.as_millis())).transpose().ok()?;
124            let min = min.map(|l| UInt::try_from(l.as_millis())).transpose().ok()?;
125
126            if is_valid_lifetime_combination(min, max) { Some(Self { min, max }) } else { None }
127        }
128
129        /// Create a new [`LifetimeLimits`] object from a range.
130        ///
131        /// Returns `None` if the duration of one of the limits, expressed as milliseconds, doesn't
132        /// fall into the [0, (2^53)-1] range, or if the lower bound of the range is bigger than the
133        /// upper bound, i.e. `10..0`.
134        ///
135        /// # Examples
136        ///
137        /// ```
138        /// # use std::time::Duration;
139        /// # use ruma_client_api::retention::get_retention_configuration::unstable::LifetimeLimits;
140        /// # fn doctest() -> Option<()> {
141        /// let content =
142        ///     LifetimeLimits::from_range(Duration::from_hours(24)..Duration::from_hours(24 * 10))?;
143        /// # None
144        /// # }
145        /// ```
146        pub fn from_range(lifetime_range: impl RangeBounds<Duration>) -> Option<Self> {
147            let min_lifetime = match lifetime_range.start_bound() {
148                std::ops::Bound::Included(v) => Some(*v),
149                std::ops::Bound::Excluded(v) => Some(v.saturating_add(Duration::from_millis(1))),
150                std::ops::Bound::Unbounded => None,
151            };
152
153            let max_lifetime = match lifetime_range.end_bound() {
154                std::ops::Bound::Included(v) => Some(*v),
155                std::ops::Bound::Excluded(v) => Some(v.saturating_sub(Duration::from_millis(1))),
156                std::ops::Bound::Unbounded => None,
157            };
158
159            Self::new_impl(min_lifetime, max_lifetime)
160        }
161
162        /// Sets the maximum value that a retention policy limit is allowed to have.
163        ///
164        /// Returns `None` if the given limit, expressed as milliseconds, doesn't fall into the [0,
165        /// (2^53)-1] range, or if the limits don't adhere to the `max` < `min` constraint.
166        pub fn at_most(self, max: Duration) -> Option<Self> {
167            let min = self.min();
168            Self::new_impl(min, Some(max))
169        }
170
171        /// Sets the minimum value that a retention policy limit is allowed to have.
172        ///
173        /// Returns `None` if the given limit, expressed as milliseconds, doesn't fall into the [0,
174        /// (2^53)-1] range, or if the limits don't adhere to the `max` < `min` constraint.
175        pub fn at_least(self, min: Duration) -> Option<Self> {
176            let max = self.max();
177            Self::new_impl(Some(min), max)
178        }
179
180        /// Get the minimum accepted value of this limit.
181        pub fn min(&self) -> Option<Duration> {
182            self.min.map(|l| Duration::from_millis(l.into()))
183        }
184
185        /// Get the maximum accepted value of this limit.
186        pub fn max(&self) -> Option<Duration> {
187            self.max.map(|l| Duration::from_millis(l.into()))
188        }
189    }
190
191    impl<'de> Deserialize<'de> for LifetimeLimits {
192        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
193        where
194            D: serde::Deserializer<'de>,
195        {
196            #[derive(Deserialize)]
197            struct Helper {
198                min: Option<UInt>,
199                max: Option<UInt>,
200            }
201
202            let Helper { min, max } = Helper::deserialize(deserializer)?;
203
204            if is_valid_lifetime_combination(min, max) {
205                Ok(Self { min, max })
206            } else {
207                Err(serde::de::Error::custom(
208                "Invalid lifetime limits, the max limit must always be higher or equal to the min limit."
209                    .to_owned(),
210            ))
211            }
212        }
213    }
214}