ruma-common 0.9.3

Common types for other ruma crates.
Documentation
use js_int::Int;
use serde::{Deserialize, Serialize};
use serde_json::{from_str as from_json_str, value::RawValue as RawJsonValue};

#[cfg(feature = "unstable-msc2675")]
use super::relation::Relations;
use super::{room::redaction::SyncRoomRedactionEvent, StateEventContent};
use crate::{serde::Raw, OwnedTransactionId};

/// Extra information about a message event that is not incorporated into the event's hash.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct MessageLikeUnsigned {
    /// The time in milliseconds that has elapsed since the event was sent.
    ///
    /// This field is generated by the local homeserver, and may be incorrect if the local time on
    /// at least one of the two servers is out of sync, which can cause the age to either be
    /// negative or greater than it actually is.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub age: Option<Int>,

    /// The client-supplied transaction ID, if the client being given the event is the same one
    /// which sent it.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub transaction_id: Option<OwnedTransactionId>,

    /// Server-compiled information from other events relating to this event.
    #[cfg(feature = "unstable-msc2675")]
    #[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")]
    pub relations: Option<Relations>,
}

impl MessageLikeUnsigned {
    /// Create a new `Unsigned` with fields set to `None`.
    pub fn new() -> Self {
        Self::default()
    }

    /// Whether this unsigned data is empty (all fields are `None`).
    ///
    /// This method is used to determine whether to skip serializing the `unsigned` field in room
    /// events. Do not use it to determine whether an incoming `unsigned` field was present - it
    /// could still have been present but contained none of the known fields.
    pub fn is_empty(&self) -> bool {
        #[cfg(not(feature = "unstable-msc2675"))]
        {
            self.age.is_none() && self.transaction_id.is_none()
        }

        #[cfg(feature = "unstable-msc2675")]
        {
            self.age.is_none() && self.transaction_id.is_none() && self.relations.is_none()
        }
    }
}

/// Extra information about a state event that is not incorporated into the event's hash.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct StateUnsigned<C: StateEventContent> {
    /// The time in milliseconds that has elapsed since the event was sent.
    ///
    /// This field is generated by the local homeserver, and may be incorrect if the local time on
    /// at least one of the two servers is out of sync, which can cause the age to either be
    /// negative or greater than it actually is.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub age: Option<Int>,

    /// The client-supplied transaction ID, if the client being given the event is the same one
    /// which sent it.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub transaction_id: Option<OwnedTransactionId>,

    /// Optional previous content of the event.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub prev_content: Option<C>,

    /// Server-compiled information from other events relating to this event.
    #[cfg(feature = "unstable-msc2675")]
    #[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")]
    pub relations: Option<Relations>,
}

impl<C: StateEventContent> StateUnsigned<C> {
    /// Create a new `Unsigned` with fields set to `None`.
    pub fn new() -> Self {
        Self {
            age: None,
            transaction_id: None,
            prev_content: None,
            #[cfg(feature = "unstable-msc2675")]
            relations: None,
        }
    }

    /// Whether this unsigned data is empty (all fields are `None`).
    ///
    /// This method is used to determine whether to skip serializing the `unsigned` field in room
    /// events. Do not use it to determine whether an incoming `unsigned` field was present - it
    /// could still have been present but contained none of the known fields.
    pub fn is_empty(&self) -> bool {
        #[cfg(not(feature = "unstable-msc2675"))]
        {
            self.age.is_none() && self.transaction_id.is_none() && self.prev_content.is_none()
        }

        #[cfg(feature = "unstable-msc2675")]
        {
            self.age.is_none()
                && self.transaction_id.is_none()
                && self.prev_content.is_none()
                && self.relations.is_none()
        }
    }
}

/// Helper functions for proc-macro code.
///
/// Needs to be public for UI tests.
#[doc(hidden)]
impl<C: StateEventContent> StateUnsigned<C> {
    pub fn _from_parts(event_type: &str, object: &RawJsonValue) -> serde_json::Result<Self> {
        #[derive(Deserialize)]
        #[serde(bound = "")] // Disable default C: Deserialize bound
        struct WithRawPrevContent<C> {
            #[serde(skip_serializing_if = "Option::is_none")]
            age: Option<Int>,
            #[serde(skip_serializing_if = "Option::is_none")]
            transaction_id: Option<OwnedTransactionId>,
            prev_content: Option<Raw<C>>,
            #[cfg(feature = "unstable-msc2675")]
            #[serde(rename = "m.relations", skip_serializing_if = "Option::is_none")]
            relations: Option<Relations>,
        }

        let raw: WithRawPrevContent<C> = from_json_str(object.get())?;
        let prev_content =
            raw.prev_content.map(|r| r.deserialize_content(event_type.into())).transpose()?;

        Ok(Self {
            age: raw.age,
            transaction_id: raw.transaction_id,
            #[cfg(feature = "unstable-msc2675")]
            relations: raw.relations,
            prev_content,
        })
    }

    pub fn _map_prev_content<T>(&self, f: impl FnOnce(&C) -> T) -> StateUnsigned<T>
    where
        T: StateEventContent,
    {
        StateUnsigned {
            age: self.age,
            transaction_id: self.transaction_id.clone(),
            prev_content: self.prev_content.as_ref().map(f),
            #[cfg(feature = "unstable-msc2675")]
            relations: self.relations.clone(),
        }
    }
}

impl<C: StateEventContent> Default for StateUnsigned<C> {
    fn default() -> Self {
        Self::new()
    }
}

/// Extra information about a redacted event that is not incorporated into the event's hash.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct RedactedUnsigned {
    /// The event that redacted this event, if any.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub redacted_because: Option<Box<SyncRoomRedactionEvent>>,
}

impl RedactedUnsigned {
    /// Create a new `RedactedUnsigned` with field set to `None`.
    pub fn new() -> Self {
        Self::default()
    }

    /// Create a new `RedactedUnsigned` with the given redacted because.
    pub fn new_because(redacted_because: Box<SyncRoomRedactionEvent>) -> Self {
        Self { redacted_because: Some(redacted_because) }
    }

    /// Whether this unsigned data is empty (`redacted_because` is `None`).
    ///
    /// This method is used to determine whether to skip serializing the `unsigned` field in
    /// redacted room events. Do not use it to determine whether an incoming `unsigned` field
    /// was present - it could still have been present but contained none of the known fields.
    pub fn is_empty(&self) -> bool {
        self.redacted_because.is_none()
    }
}