ruma_events/relation.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
//! Types describing [relationships between events].
//!
//! [relationships between events]: https://spec.matrix.org/latest/client-server-api/#forming-relationships-between-events
use std::fmt::Debug;
use js_int::UInt;
use ruma_common::{
serde::{JsonObject, Raw, StringEnum},
OwnedEventId,
};
use serde::{Deserialize, Serialize};
use super::AnyMessageLikeEvent;
use crate::PrivOwnedStr;
mod rel_serde;
/// Information about the event a [rich reply] is replying to.
///
/// [rich reply]: https://spec.matrix.org/latest/client-server-api/#rich-replies
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct InReplyTo {
/// The event being replied to.
pub event_id: OwnedEventId,
}
impl InReplyTo {
/// Creates a new `InReplyTo` with the given event ID.
pub fn new(event_id: OwnedEventId) -> Self {
Self { event_id }
}
}
/// An [annotation] for an event.
///
/// [annotation]: https://spec.matrix.org/latest/client-server-api/#event-annotations-and-reactions
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[serde(tag = "rel_type", rename = "m.annotation")]
pub struct Annotation {
/// The event that is being annotated.
pub event_id: OwnedEventId,
/// A string that indicates the annotation being applied.
///
/// When sending emoji reactions, this field should include the colourful variation-16 when
/// applicable.
///
/// Clients should render reactions that have a long `key` field in a sensible manner.
pub key: String,
}
impl Annotation {
/// Creates a new `Annotation` with the given event ID and key.
pub fn new(event_id: OwnedEventId, key: String) -> Self {
Self { event_id, key }
}
}
/// The content of a [replacement] relation.
///
/// [replacement]: https://spec.matrix.org/latest/client-server-api/#event-replacements
#[derive(Clone, Debug)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct Replacement<C> {
/// The ID of the event being replaced.
pub event_id: OwnedEventId,
/// New content.
pub new_content: C,
}
impl<C> Replacement<C> {
/// Creates a new `Replacement` with the given event ID and new content.
pub fn new(event_id: OwnedEventId, new_content: C) -> Self {
Self { event_id, new_content }
}
}
/// The content of a [thread] relation.
///
/// [thread]: https://spec.matrix.org/latest/client-server-api/#threading
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[serde(tag = "rel_type", rename = "m.thread")]
pub struct Thread {
/// The ID of the root message in the thread.
pub event_id: OwnedEventId,
/// A reply relation.
///
/// If this event is a reply and belongs to a thread, this points to the message that is being
/// replied to, and `is_falling_back` must be set to `false`.
///
/// If this event is not a reply, this is used as a fallback mechanism for clients that do not
/// support threads. This should point to the latest message-like event in the thread and
/// `is_falling_back` must be set to `true`.
#[serde(rename = "m.in_reply_to", skip_serializing_if = "Option::is_none")]
pub in_reply_to: Option<InReplyTo>,
/// Whether the `m.in_reply_to` field is a fallback for older clients or a genuine reply in a
/// thread.
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
pub is_falling_back: bool,
}
impl Thread {
/// Convenience method to create a regular `Thread` relation with the given root event ID and
/// latest message-like event ID.
pub fn plain(event_id: OwnedEventId, latest_event_id: OwnedEventId) -> Self {
Self { event_id, in_reply_to: Some(InReplyTo::new(latest_event_id)), is_falling_back: true }
}
/// Convenience method to create a regular `Thread` relation with the given root event ID and
/// *without* the recommended reply fallback.
pub fn without_fallback(event_id: OwnedEventId) -> Self {
Self { event_id, in_reply_to: None, is_falling_back: false }
}
/// Convenience method to create a reply `Thread` relation with the given root event ID and
/// replied-to event ID.
pub fn reply(event_id: OwnedEventId, reply_to_event_id: OwnedEventId) -> Self {
Self {
event_id,
in_reply_to: Some(InReplyTo::new(reply_to_event_id)),
is_falling_back: false,
}
}
}
/// A bundled thread.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct BundledThread {
/// The latest event in the thread.
pub latest_event: Raw<AnyMessageLikeEvent>,
/// The number of events in the thread.
pub count: UInt,
/// Whether the current logged in user has participated in the thread.
pub current_user_participated: bool,
}
impl BundledThread {
/// Creates a new `BundledThread` with the given event, count and user participated flag.
pub fn new(
latest_event: Raw<AnyMessageLikeEvent>,
count: UInt,
current_user_participated: bool,
) -> Self {
Self { latest_event, count, current_user_participated }
}
}
/// A [reference] to another event.
///
/// [reference]: https://spec.matrix.org/latest/client-server-api/#reference-relations
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[serde(tag = "rel_type", rename = "m.reference")]
pub struct Reference {
/// The ID of the event being referenced.
pub event_id: OwnedEventId,
}
impl Reference {
/// Creates a new `Reference` with the given event ID.
pub fn new(event_id: OwnedEventId) -> Self {
Self { event_id }
}
}
/// A bundled reference.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct BundledReference {
/// The ID of the event referencing this event.
pub event_id: OwnedEventId,
}
impl BundledReference {
/// Creates a new `BundledThread` with the given event ID.
pub fn new(event_id: OwnedEventId) -> Self {
Self { event_id }
}
}
/// A chunk of references.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct ReferenceChunk {
/// A batch of bundled references.
pub chunk: Vec<BundledReference>,
}
impl ReferenceChunk {
/// Creates a new `ReferenceChunk` with the given chunk.
pub fn new(chunk: Vec<BundledReference>) -> Self {
Self { chunk }
}
}
/// [Bundled aggregations] of related child events of a message-like event.
///
/// [Bundled aggregations]: https://spec.matrix.org/latest/client-server-api/#aggregations-of-child-events
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct BundledMessageLikeRelations<E> {
/// Replacement relation.
#[serde(rename = "m.replace", skip_serializing_if = "Option::is_none")]
pub replace: Option<Box<E>>,
/// Set when the above fails to deserialize.
///
/// Intentionally *not* public.
#[serde(skip_serializing)]
has_invalid_replacement: bool,
/// Thread relation.
#[serde(rename = "m.thread", skip_serializing_if = "Option::is_none")]
pub thread: Option<Box<BundledThread>>,
/// Reference relations.
#[serde(rename = "m.reference", skip_serializing_if = "Option::is_none")]
pub reference: Option<Box<ReferenceChunk>>,
}
impl<E> BundledMessageLikeRelations<E> {
/// Creates a new empty `BundledMessageLikeRelations`.
pub const fn new() -> Self {
Self { replace: None, has_invalid_replacement: false, thread: None, reference: None }
}
/// Whether this bundle contains a replacement relation.
///
/// This may be `true` even if the `replace` field is `None`, because Matrix versions prior to
/// 1.7 had a different incompatible format for bundled replacements. Use this method to check
/// whether an event was replaced. If this returns `true` but `replace` is `None`, use one of
/// the endpoints from `ruma::api::client::relations` to fetch the relation details.
pub fn has_replacement(&self) -> bool {
self.replace.is_some() || self.has_invalid_replacement
}
/// Returns `true` if all fields are empty.
pub fn is_empty(&self) -> bool {
self.replace.is_none() && self.thread.is_none() && self.reference.is_none()
}
/// Transform `BundledMessageLikeRelations<E>` to `BundledMessageLikeRelations<T>` using the
/// given closure to convert the `replace` field if it is `Some(_)`.
pub(crate) fn map_replace<T>(self, f: impl FnOnce(E) -> T) -> BundledMessageLikeRelations<T> {
let Self { replace, has_invalid_replacement, thread, reference } = self;
let replace = replace.map(|r| Box::new(f(*r)));
BundledMessageLikeRelations { replace, has_invalid_replacement, thread, reference }
}
}
impl<E> Default for BundledMessageLikeRelations<E> {
fn default() -> Self {
Self::new()
}
}
/// [Bundled aggregations] of related child events of a state event.
///
/// [Bundled aggregations]: https://spec.matrix.org/latest/client-server-api/#aggregations-of-child-events
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct BundledStateRelations {
/// Thread relation.
#[serde(rename = "m.thread", skip_serializing_if = "Option::is_none")]
pub thread: Option<Box<BundledThread>>,
/// Reference relations.
#[serde(rename = "m.reference", skip_serializing_if = "Option::is_none")]
pub reference: Option<Box<ReferenceChunk>>,
}
impl BundledStateRelations {
/// Creates a new empty `BundledStateRelations`.
pub const fn new() -> Self {
Self { thread: None, reference: None }
}
/// Returns `true` if all fields are empty.
pub fn is_empty(&self) -> bool {
self.thread.is_none() && self.reference.is_none()
}
}
/// Relation types as defined in `rel_type` of an `m.relates_to` field.
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
#[derive(Clone, PartialEq, Eq, StringEnum)]
#[ruma_enum(rename_all = "m.snake_case")]
#[non_exhaustive]
pub enum RelationType {
/// `m.annotation`, an annotation, principally used by reactions.
Annotation,
/// `m.replace`, a replacement.
Replacement,
/// `m.thread`, a participant to a thread.
Thread,
/// `m.reference`, a reference to another event.
Reference,
#[doc(hidden)]
_Custom(PrivOwnedStr),
}
/// The payload for a custom relation.
#[doc(hidden)]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(transparent)]
pub struct CustomRelation(pub(super) JsonObject);
impl CustomRelation {
pub(super) fn rel_type(&self) -> Option<RelationType> {
Some(self.0.get("rel_type")?.as_str()?.into())
}
}