ruma_events/
lib.rs

1#![doc(html_favicon_url = "https://ruma.dev/favicon.ico")]
2#![doc(html_logo_url = "https://ruma.dev/images/logo.png")]
3//! (De)serializable types for the events in the [Matrix](https://matrix.org) specification.
4//! These types are used by other Ruma crates.
5//!
6//! All data exchanged over Matrix is expressed as an event.
7//! Different event types represent different actions, such as joining a room or sending a message.
8//! Events are stored and transmitted as simple JSON structures.
9//! While anyone can create a new event type for their own purposes, the Matrix specification
10//! defines a number of event types which are considered core to the protocol.
11//! This module contains Rust types for all of the event types defined by the specification and
12//! facilities for extending the event system for custom event types.
13//!
14//! # Core event types
15//!
16//! This module includes Rust types for all event types in the Matrix specification.
17//! To better organize the crate, these types live in separate modules with a hierarchy that matches
18//! the reverse domain name notation of the event type. For example, the `m.room.message` event
19//! lives at `ruma::events::room::message::RoomMessageEvent`. Each type's module also contains a
20//! Rust type for that event type's `content` field, and any other supporting types required by the
21//! event's other fields.
22//!
23//! # Extending Ruma with custom events
24//!
25//! For our examples we will start with a simple custom state event. `ruma_event`
26//! specifies the state event's `type` and its `kind`.
27//!
28//! ```rust
29//! use ruma_events::macros::EventContent;
30//! use serde::{Deserialize, Serialize};
31//!
32//! #[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
33//! #[ruma_event(type = "org.example.event", kind = State, state_key_type = String)]
34//! pub struct ExampleContent {
35//!     field: String,
36//! }
37//! ```
38//!
39//! This can be used with events structs, such as passing it into
40//! `ruma::api::client::state::send_state_event`'s `Request`.
41//!
42//! As a more advanced example we create a reaction message event. For this event we will use a
43//! [`OriginalSyncMessageLikeEvent`] struct but any [`OriginalMessageLikeEvent`] struct would work.
44//!
45//! ```rust
46//! use ruma_common::OwnedEventId;
47//! use ruma_events::{macros::EventContent, OriginalSyncMessageLikeEvent};
48//! use serde::{Deserialize, Serialize};
49//!
50//! #[derive(Clone, Debug, Deserialize, Serialize)]
51//! #[serde(tag = "rel_type")]
52//! pub enum RelatesTo {
53//!     #[serde(rename = "m.annotation")]
54//!     Annotation {
55//!         /// The event this reaction relates to.
56//!         event_id: OwnedEventId,
57//!         /// The displayable content of the reaction.
58//!         key: String,
59//!     },
60//!
61//!     /// Since this event is not fully specified in the Matrix spec
62//!     /// it may change or types may be added, we are ready!
63//!     #[serde(rename = "m.whatever")]
64//!     Whatever,
65//! }
66//!
67//! /// The payload for our reaction event.
68//! #[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
69//! #[ruma_event(type = "m.reaction", kind = MessageLike)]
70//! pub struct ReactionEventContent {
71//!     #[serde(rename = "m.relates_to")]
72//!     pub relates_to: RelatesTo,
73//! }
74//!
75//! let json = serde_json::json!({
76//!     "content": {
77//!         "m.relates_to": {
78//!             "event_id": "$xxxx-xxxx",
79//!             "key": "👍",
80//!             "rel_type": "m.annotation"
81//!         }
82//!     },
83//!     "event_id": "$xxxx-xxxx",
84//!     "origin_server_ts": 1,
85//!     "sender": "@someone:example.org",
86//!     "type": "m.reaction",
87//!     "unsigned": {
88//!         "age": 85
89//!     }
90//! });
91//!
92//! // The downside of this event is we cannot use it with event enums,
93//! // but could be deserialized from a `Raw<_>` that has failed to deserialize.
94//! assert!(matches!(
95//!     serde_json::from_value::<OriginalSyncMessageLikeEvent<ReactionEventContent>>(json),
96//!     Ok(OriginalSyncMessageLikeEvent {
97//!         content: ReactionEventContent {
98//!             relates_to: RelatesTo::Annotation { key, .. },
99//!         },
100//!         ..
101//!     }) if key == "👍"
102//! ));
103//! ```
104
105#![warn(missing_docs)]
106
107#[cfg(feature = "unstable-uniffi")]
108uniffi::setup_scaffolding!();
109
110use std::{collections::BTreeSet, fmt};
111
112use ruma_common::{EventEncryptionAlgorithm, OwnedUserId, room_version_rules::RedactionRules};
113use serde::{Deserialize, Serialize, Serializer, de::IgnoredAny};
114
115// Needs to be public for trybuild tests
116#[doc(hidden)]
117pub mod _custom;
118mod content;
119mod enums;
120mod kinds;
121mod state_key;
122mod unsigned;
123
124// So event macros work inside this crate.
125extern crate self as ruma_events;
126
127/// Re-exports used by macro-generated code.
128///
129/// It is not considered part of this module's public API.
130#[doc(hidden)]
131pub mod exports {
132    pub use ruma_common;
133    pub use ruma_macros;
134    pub use serde;
135    pub use serde_json;
136}
137
138/// Re-export of all the derives needed to create your own event types.
139pub mod macros {
140    pub use ruma_macros::{Event, EventContent};
141}
142
143#[cfg(feature = "unstable-msc3927")]
144pub mod audio;
145#[cfg(feature = "unstable-msc3489")]
146pub mod beacon;
147#[cfg(feature = "unstable-msc3489")]
148pub mod beacon_info;
149pub mod call;
150pub mod direct;
151#[cfg(feature = "unstable-msc4359")]
152pub mod do_not_disturb;
153pub mod dummy;
154#[cfg(feature = "unstable-msc3954")]
155pub mod emote;
156#[cfg(feature = "unstable-msc3956")]
157pub mod encrypted;
158#[cfg(feature = "unstable-msc3551")]
159pub mod file;
160pub mod forwarded_room_key;
161pub mod fully_read;
162pub mod identity_server;
163pub mod ignored_user_list;
164#[cfg(feature = "unstable-msc3552")]
165pub mod image;
166#[cfg(feature = "unstable-msc2545")]
167pub mod image_pack;
168pub mod invite_permission_config;
169pub mod key;
170#[cfg(feature = "unstable-msc3488")]
171pub mod location;
172pub mod marked_unread;
173#[cfg(feature = "unstable-msc4278")]
174pub mod media_preview_config;
175#[cfg(feature = "unstable-msc4171")]
176pub mod member_hints;
177pub mod message;
178pub mod policy;
179#[cfg(feature = "unstable-msc3381")]
180pub mod poll;
181pub mod presence;
182pub mod push_rules;
183pub mod reaction;
184pub mod receipt;
185pub mod recent_emoji;
186pub mod relation;
187pub mod room;
188pub mod room_key;
189#[cfg(feature = "unstable-msc4268")]
190pub mod room_key_bundle;
191pub mod room_key_request;
192#[cfg(feature = "unstable-msc4310")]
193pub mod rtc;
194pub mod secret;
195pub mod secret_storage;
196pub mod space;
197#[cfg(feature = "unstable-msc3230")]
198pub mod space_order;
199pub mod sticker;
200pub mod tag;
201pub mod typing;
202#[cfg(feature = "unstable-msc3553")]
203pub mod video;
204#[cfg(feature = "unstable-msc3245")]
205pub mod voice;
206
207pub use self::{
208    content::*,
209    enums::*,
210    kinds::*,
211    relation::{BundledMessageLikeRelations, BundledStateRelations},
212    state_key::EmptyStateKey,
213    unsigned::{MessageLikeUnsigned, RedactedUnsigned, StateUnsigned, UnsignedRoomRedactionEvent},
214};
215
216/// Trait to define the behavior of redact an event's content object.
217pub trait RedactContent {
218    /// The redacted form of the event's content.
219    type Redacted;
220
221    /// Transform `self` into a redacted form (removing most or all fields) according to the spec.
222    ///
223    /// A small number of events have room-version specific redaction behavior, so a
224    /// [`RedactionRules`] has to be specified.
225    fn redact(self, rules: &RedactionRules) -> Self::Redacted;
226}
227
228/// Helper struct to determine the event kind from a `serde_json::value::RawValue`.
229#[doc(hidden)]
230#[derive(Deserialize)]
231#[allow(clippy::exhaustive_structs)]
232pub struct EventTypeDeHelper<'a> {
233    #[serde(borrow, rename = "type")]
234    pub ev_type: std::borrow::Cow<'a, str>,
235}
236
237/// Helper struct to determine if an event has been redacted.
238#[doc(hidden)]
239#[derive(Deserialize)]
240#[allow(clippy::exhaustive_structs)]
241pub struct RedactionDeHelper {
242    /// Used to check whether redacted_because exists.
243    pub unsigned: Option<UnsignedDeHelper>,
244}
245
246#[doc(hidden)]
247#[derive(Deserialize)]
248#[allow(clippy::exhaustive_structs)]
249pub struct UnsignedDeHelper {
250    /// This is the field that signals an event has been redacted.
251    pub redacted_because: Option<IgnoredAny>,
252}
253
254/// Helper function for erroring when trying to serialize an event enum _Custom variant that can
255/// only be created by deserializing from an unknown event type.
256#[doc(hidden)]
257#[allow(clippy::ptr_arg)]
258pub fn serialize_custom_event_error<T, S: Serializer>(_: &T, _: S) -> Result<S::Ok, S::Error> {
259    Err(serde::ser::Error::custom(
260        "Failed to serialize event [content] enum: Unknown event type.\n\
261         To send custom events, turn them into `Raw<EnumType>` by going through
262         `serde_json::value::to_raw_value` and `Raw::from_json`.",
263    ))
264}
265
266/// Describes whether the event mentions other users or the room.
267#[derive(Clone, Debug, Default, Serialize, Deserialize)]
268#[non_exhaustive]
269pub struct Mentions {
270    /// The list of mentioned users.
271    ///
272    /// Defaults to an empty `BTreeSet`.
273    #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
274    pub user_ids: BTreeSet<OwnedUserId>,
275
276    /// Whether the whole room is mentioned.
277    ///
278    /// Defaults to `false`.
279    #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
280    pub room: bool,
281}
282
283impl Mentions {
284    /// Create a `Mentions` with the default values.
285    pub fn new() -> Self {
286        Self::default()
287    }
288
289    /// Create a `Mentions` for the given user IDs.
290    pub fn with_user_ids(user_ids: impl IntoIterator<Item = OwnedUserId>) -> Self {
291        Self { user_ids: BTreeSet::from_iter(user_ids), ..Default::default() }
292    }
293
294    /// Create a `Mentions` for a room mention.
295    pub fn with_room_mention() -> Self {
296        Self { room: true, ..Default::default() }
297    }
298
299    fn add(&mut self, mentions: Self) {
300        self.user_ids.extend(mentions.user_ids);
301        self.room |= mentions.room;
302    }
303}
304
305// Wrapper around `Box<str>` that cannot be used in a meaningful way outside of
306// this crate. Used for string enums because their `_Custom` variant can't be
307// truly private (only `#[doc(hidden)]`).
308#[doc(hidden)]
309#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
310pub struct PrivOwnedStr(Box<str>);
311
312impl fmt::Debug for PrivOwnedStr {
313    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314        self.0.fmt(f)
315    }
316}
317
318// Wrapper around `Box<str>` for transferring `PrivOwnedStr` over UniFFI.
319// We cannot derive `PrivOwnedStr` from `uniffi::Object` directly because
320// that would require wrapping it in an `Arc` inside the `_Custom` variants.
321#[cfg_attr(feature = "unstable-uniffi", derive(uniffi::Object))]
322#[doc(hidden)]
323#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
324pub struct PrivateString(Box<str>);
325
326#[cfg(feature = "unstable-uniffi")]
327uniffi::custom_type!(PrivOwnedStr, std::sync::Arc<PrivateString> , {
328    lower: |value| std::sync::Arc::new(PrivateString(value.0)),
329    try_lift: |value| Ok(PrivOwnedStr(value.0.clone())),
330});