1use std::ops::Not as _;
8
9use arminius_lib_db::{DatabaseError, ResultExt as _};
10use ruma::{
11 OwnedDeviceId, RoomId, UInt, UserId,
12 api::client::filter::{
13 EventFormat, Filter, FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter,
14 UrlFilter,
15 },
16};
17use sqlx::{Type, prelude::FromRow};
18
19use crate::types::DbUlid;
20
21#[derive(Debug, Type)]
24#[sqlx(type_name = "room_event_filter")]
25pub struct DbRoomEventFilter {
26 limit: Option<i32>,
33 not_rooms: Option<Vec<String>>,
35 not_senders: Option<Vec<String>>,
37 not_types: Option<Vec<String>>,
39 rooms: Option<Vec<String>>,
41 senders: Option<Vec<String>>,
43 types: Option<Vec<String>>,
45 contains_url: Option<bool>,
49 include_redundant_members: Option<bool>,
53 lazy_load_members: Option<bool>,
55 unread_thread_notifications: Option<bool>,
58}
59
60#[derive(Debug, Type)]
62#[sqlx(type_name = "room_filter")]
63pub struct DbRoomFilter {
64 pub account_data: Option<DbRoomEventFilter>,
66 pub ephemeral: Option<DbRoomEventFilter>,
69 pub state: Option<DbRoomEventFilter>,
71 pub timeline: Option<DbRoomEventFilter>,
73 pub include_leave: Option<bool>,
75 pub not_rooms: Option<Vec<String>>,
77 pub rooms: Option<Vec<String>>,
79}
80
81#[derive(Debug, Type)]
83#[sqlx(type_name = "event_filter")]
84pub struct DbEventFilter {
85 pub limit: Option<i32>,
92 pub not_senders: Option<Vec<String>>,
94 pub not_types: Option<Vec<String>>,
96 pub senders: Option<Vec<String>>,
98 pub types: Option<Vec<String>>,
100}
101
102#[derive(Debug, Type)]
105#[sqlx(type_name = "event_format_enum", rename_all = "lowercase")]
106pub enum DbEventFormat {
107 Client,
109 Federation,
112}
113
114impl TryFrom<DbRoomEventFilter> for Filter {
115 type Error = DatabaseError;
116
117 fn try_from(filter: DbRoomEventFilter) -> Result<Self, Self::Error> {
118 let mut ruma_filter = Self::empty();
119 ruma_filter.not_types = filter.not_types.unwrap_or_default();
120 ruma_filter.limit = filter
121 .limit
122 .map(|limit| {
123 UInt::try_from(limit).invalid_data(&format!(
124 "the limit number of '{limit}' is greater than the maximum number that can fit into a non negative f64"
125 ))
126 })
127 .transpose()?;
128 if let Some(senders) = filter.senders {
129 ruma_filter.senders = Some(
130 senders
131 .into_iter()
132 .map(|raw_user_id| {
133 UserId::parse(&raw_user_id).invalid_data(&format!(
134 "the user_id retrieved from the database for '{raw_user_id}' is not a valid user ID"
135 ))
136 })
137 .collect::<Result<Vec<_>, _>>()?,
138 );
139 }
140 ruma_filter.not_senders = filter
141 .not_senders
142 .unwrap_or_default()
143 .into_iter()
144 .map(|raw_user_id| {
145 UserId::parse(&raw_user_id).invalid_data(&format!(
146 "the user_id retrieved from the database for '{raw_user_id}' is not a valid user ID"
147 ))
148 })
149 .collect::<Result<Vec<_>, _>>()?;
150
151 ruma_filter.types = filter.types;
152 Ok(ruma_filter)
153 }
154}
155
156impl From<DbEventFormat> for EventFormat {
157 fn from(event_format: DbEventFormat) -> Self {
158 match event_format {
159 DbEventFormat::Client => EventFormat::Client,
160 DbEventFormat::Federation => EventFormat::Federation,
161 }
162 }
163}
164
165impl TryFrom<DbRoomEventFilter> for RoomEventFilter {
166 type Error = DatabaseError;
167
168 fn try_from(room_event_filter: DbRoomEventFilter) -> Result<Self, Self::Error> {
169 let mut ruma_room_event_filter = Self::empty();
170 ruma_room_event_filter.types = room_event_filter.types;
171 ruma_room_event_filter.not_types = room_event_filter.not_types.unwrap_or_default();
172 ruma_room_event_filter.not_rooms = room_event_filter
173 .not_rooms
174 .unwrap_or_default()
175 .into_iter()
176 .map(|raw_room_id| {
177 RoomId::parse(&raw_room_id).invalid_data(&format!(
178 "the room id from database '{raw_room_id}' is not a valid room id"
179 ))
180 })
181 .collect::<Result<Vec<_>, _>>()?;
182 ruma_room_event_filter.rooms = room_event_filter
183 .rooms
184 .map(|rooms| {
185 rooms
186 .into_iter()
187 .map(|raw_room_id| {
188 RoomId::parse(&raw_room_id).invalid_data(&format!(
189 "the room id from database '{raw_room_id}' is not a valid room id"
190 ))
191 })
192 .collect::<Result<Vec<_>, _>>()
193 })
194 .transpose()?;
195
196 ruma_room_event_filter.not_senders = room_event_filter
197 .not_senders
198 .unwrap_or_default()
199 .into_iter()
200 .map(|raw_user_id| {
201 UserId::parse(&raw_user_id).invalid_data(&format!(
202 "the user id from database '{raw_user_id}' is not a valid user id"
203 ))
204 })
205 .collect::<Result<Vec<_>, _>>()?;
206
207 ruma_room_event_filter.senders = room_event_filter
208 .senders
209 .map(|senders| {
210 senders
211 .into_iter()
212 .map(|raw_user_id| {
213 UserId::parse(&raw_user_id).invalid_data(&format!(
214 "the user id from database '{raw_user_id}' is not a valid user id"
215 ))
216 })
217 .collect::<Result<Vec<_>, _>>()
218 })
219 .transpose()?;
220 ruma_room_event_filter.limit =
221 room_event_filter.limit
222 .map(|limit| {
223 UInt::try_from(limit).invalid_data(&format!(
224 "the limit number of '{limit}' is greater than the maximum number that can fit into a non negative f64"
225 ))
226 }).transpose()?;
227 ruma_room_event_filter.url_filter = room_event_filter.contains_url.map(|contains_url| {
228 if contains_url { UrlFilter::EventsWithUrl } else { UrlFilter::EventsWithoutUrl }
229 });
230 ruma_room_event_filter.lazy_load_options =
231 if room_event_filter.lazy_load_members.is_some_and(|lazy_load| lazy_load) {
232 LazyLoadOptions::Enabled {
233 include_redundant_members: room_event_filter
234 .include_redundant_members
235 .unwrap_or_default(),
236 }
237 } else {
238 LazyLoadOptions::Disabled
239 };
240
241 if let Some(unread_thread_notifications) = room_event_filter.unread_thread_notifications {
242 ruma_room_event_filter.unread_thread_notifications = unread_thread_notifications;
243 }
244 Ok(ruma_room_event_filter)
245 }
246}
247
248impl TryFrom<DbRoomFilter> for RoomFilter {
249 type Error = DatabaseError;
250
251 fn try_from(room_filter: DbRoomFilter) -> Result<Self, Self::Error> {
252 let mut ruma_room_filter = Self::empty();
253 ruma_room_filter.include_leave = room_filter.include_leave.unwrap_or_default();
254
255 if let Some(account_data) = room_filter.account_data {
256 ruma_room_filter.account_data = account_data.try_into()?;
257 }
258 if let Some(ephemeral) = room_filter.ephemeral {
259 ruma_room_filter.ephemeral = ephemeral.try_into()?;
260 }
261 if let Some(state) = room_filter.state {
262 ruma_room_filter.state = state.try_into()?;
263 }
264 if let Some(timeline) = room_filter.timeline {
265 ruma_room_filter.timeline = timeline.try_into()?;
266 }
267 ruma_room_filter.rooms = room_filter
268 .rooms
269 .map(|rooms| {
270 rooms
271 .into_iter()
272 .map(|raw_room_id| {
273 RoomId::parse(&raw_room_id).invalid_data(&format!(
274 "the room id from database '{raw_room_id}' is not a valid room id"
275 ))
276 })
277 .collect::<Result<Vec<_>, _>>()
278 })
279 .transpose()?;
280 ruma_room_filter.not_rooms = room_filter
281 .not_rooms
282 .unwrap_or_default()
283 .into_iter()
284 .map(|raw_room_id| {
285 RoomId::parse(&raw_room_id).invalid_data(&format!(
286 "the room id from database '{raw_room_id}' is not a valid room id"
287 ))
288 })
289 .collect::<Result<Vec<_>, _>>()?;
290 Ok(ruma_room_filter)
291 }
292}
293
294impl From<EventFormat> for DbEventFormat {
295 fn from(format: EventFormat) -> Self {
296 match format {
297 EventFormat::Federation => DbEventFormat::Federation,
298 _ => DbEventFormat::Client, }
300 }
301}
302
303impl From<Filter> for DbEventFilter {
304 fn from(filter: Filter) -> Self {
305 Self {
306 limit: filter.limit.map(|limit| limit.try_into().unwrap_or(i32::MAX)),
307 not_senders: filter
308 .not_senders
309 .is_empty()
310 .not()
311 .then(|| filter.not_senders.into_iter().map(|sender| sender.to_string()).collect()),
312 not_types: filter.not_types.is_empty().not().then_some(filter.not_types),
313 senders: filter
314 .senders
315 .map(|sender| sender.into_iter().map(|id| id.to_string()).collect()),
316 types: filter.types,
317 }
318 }
319}
320
321impl From<RoomEventFilter> for DbRoomEventFilter {
322 fn from(filter: RoomEventFilter) -> Self {
323 let (lazy_load_members, include_redundant_members) = match filter.lazy_load_options {
324 LazyLoadOptions::Enabled { include_redundant_members } => {
325 (Some(true), Some(include_redundant_members))
326 }
327 LazyLoadOptions::Disabled => (Some(false), Some(false)),
328 };
329
330 Self {
331 limit: filter.limit.map(|limit| limit.try_into().unwrap_or(i32::MAX)),
332 not_rooms: filter
333 .not_rooms
334 .is_empty()
335 .not()
336 .then(|| filter.not_rooms.into_iter().map(|id| id.to_string()).collect()),
337 not_senders: filter
338 .not_senders
339 .is_empty()
340 .not()
341 .then(|| filter.not_senders.into_iter().map(|id| id.to_string()).collect()),
342 not_types: filter.not_types.is_empty().not().then_some(filter.not_types),
343 rooms: filter.rooms.map(|room| room.into_iter().map(|id| id.to_string()).collect()),
344 senders: filter
345 .senders
346 .map(|sender| sender.into_iter().map(|id| id.to_string()).collect()),
347 types: filter.types,
348 contains_url: match filter.url_filter {
349 Some(UrlFilter::EventsWithUrl) => Some(true),
350 Some(UrlFilter::EventsWithoutUrl) => Some(false),
351 None => None,
352 },
353 include_redundant_members,
354 lazy_load_members,
355 unread_thread_notifications: Some(filter.unread_thread_notifications),
356 }
357 }
358}
359
360impl From<RoomFilter> for DbRoomFilter {
361 fn from(filter: RoomFilter) -> Self {
362 Self {
363 account_data: filter.account_data.is_empty().not().then(|| filter.account_data.into()),
364 ephemeral: filter.ephemeral.is_empty().not().then(|| filter.ephemeral.into()),
365 state: filter.state.is_empty().not().then(|| filter.state.into()),
366 timeline: filter.timeline.is_empty().not().then(|| filter.timeline.into()),
367 include_leave: Some(filter.include_leave),
368 not_rooms: filter
369 .not_rooms
370 .is_empty()
371 .not()
372 .then(|| filter.not_rooms.into_iter().map(|id| id.to_string()).collect()),
373 rooms: filter.rooms.map(|room| room.into_iter().map(|id| id.to_string()).collect()),
374 }
375 }
376}
377
378#[derive(Debug, FromRow)]
380pub struct FilterRow {
381 pub id: DbUlid,
384 pub user_id: String,
387 pub device_id: OwnedDeviceId,
390 pub room: Option<DbRoomFilter>,
392 pub account_data: Option<DbEventFilter>,
394 pub presence: Option<DbEventFilter>,
396 pub event_format: DbEventFormat,
398 pub event_fields: Option<Vec<String>>,
400}
401
402impl TryFrom<DbEventFilter> for Filter {
403 type Error = DatabaseError;
404
405 fn try_from(filter: DbEventFilter) -> Result<Self, Self::Error> {
406 let mut ruma_filter = Self::empty();
407 ruma_filter.not_types = filter.not_types.unwrap_or_default();
408 ruma_filter.limit = filter
409 .limit
410 .map(|limit| {
411 UInt::try_from(limit).invalid_data(&format!(
412 "the limit number of '{limit}' is greater than the maximum number that can fit into a non negative f64"
413 ))
414 })
415 .transpose()?;
416 if let Some(senders) = filter.senders {
417 ruma_filter.senders = Some(
418 senders
419 .into_iter()
420 .map(|raw_user_id| {
421 UserId::parse(&raw_user_id).invalid_data(&format!(
422 "the user_id retrieved from the database for '{raw_user_id}' is not a valid user ID"
423 ))
424 })
425 .collect::<Result<Vec<_>, _>>()?,
426 );
427 }
428 ruma_filter.not_senders = filter
429 .not_senders
430 .unwrap_or_default()
431 .into_iter()
432 .map(|raw_user_id| {
433 UserId::parse(&raw_user_id).invalid_data(&format!(
434 "the user_id retrieved from the database for '{raw_user_id}' is not a valid user ID"
435 ))
436 })
437 .collect::<Result<Vec<_>, _>>()?;
438
439 ruma_filter.types = filter.types;
440 Ok(ruma_filter)
441 }
442}
443
444impl TryFrom<FilterRow> for FilterDefinition {
445 type Error = DatabaseError;
446
447 fn try_from(row: FilterRow) -> Result<Self, Self::Error> {
448 let mut filter_definition = FilterDefinition::empty();
449
450 filter_definition.event_fields = row.event_fields;
451 filter_definition.event_format = row.event_format.into();
452 if let Some(presence) = row.presence {
453 filter_definition.presence = presence.try_into()?;
454 }
455 if let Some(account_data) = row.account_data {
456 filter_definition.account_data = account_data.try_into()?;
457 }
458 if let Some(room) = row.room {
459 filter_definition.room = room.try_into()?;
460 }
461
462 Ok(filter_definition)
463 }
464}