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}