fix room version comparisons

Fixes a set of bugs introduced by 00b77144c1,
where we replaced explicit `RoomVersionId` matches with `version < V11`
comparisons. The `Ord` impl on `RoomVersionId` does not work like that,
and is in fact a lexicographic string comparison[1]. The most visible
effect of these bugs is that incoming redaction events would sometimes
be ignored.

Instead of reverting to the explicit matches, which were quite verbose,
I implemented a `RoomVersion` struct that has flags for each property
that we care about. This is similar to the approach used by ruma[2] and
synapse[3].

[1]: 7cfa3be0c6/crates/ruma-common/src/identifiers/room_version_id.rs (L136)
[2]: 7cfa3be0c6/crates/ruma-state-res/src/room_version.rs
[3]: c856ae4724/synapse/api/room_versions.py
This commit is contained in:
Benjamin Lee 2024-09-24 21:26:06 -07:00
parent ad37eae869
commit 9add9a1e96
No known key found for this signature in database
GPG key ID: FB9624E2885D55A4
8 changed files with 237 additions and 199 deletions

View file

@ -80,6 +80,8 @@ pub(crate) enum Error {
AdminCommand(&'static str),
#[error("from {0}: {1}")]
Redaction(OwnedServerName, ruma::canonical_json::RedactionError),
#[error("unsupported room version {0}")]
UnsupportedRoomVersion(ruma::RoomVersionId),
#[error("{0} in {1}")]
InconsistentRoomState(&'static str, ruma::OwnedRoomId),
}
@ -146,6 +148,10 @@ impl Error {
_ => StatusCode::BAD_REQUEST,
},
),
Self::UnsupportedRoomVersion(_) => (
ErrorKind::UnsupportedRoomVersion,
StatusCode::INTERNAL_SERVER_ERROR,
),
Self::Conflict(_) => (Unknown, StatusCode::CONFLICT),
_ => (Unknown, StatusCode::INTERNAL_SERVER_ERROR),
};

87
src/utils/room_version.rs Normal file
View file

@ -0,0 +1,87 @@
use ruma::RoomVersionId;
use crate::Error;
/// Properties that we care about in grapevine that differ between supported
/// room versions.
///
/// This is similar to [`ruma::state_res::RoomVersion`], except that it has
/// properties that are relevant to us instead of only properties relevant to
/// ruma state resolution.
///
/// When branching for different room versions, prefer constructing a
/// `RoomVersion` and branching on it's properties over branching based on the
/// [`RoomVersionId`] directly. If there is a relevant property that is not
/// already included in `RoomVersion`, add it. In particular, comparisons like
/// `room_version_id < RoomVersionId::V11` do not work, because room versions
/// do not have a defined order. Ruma implements `PartialOrd` on `RoomVersionId`
/// as a lexicographic string comparison, which is almost certainly not what you
/// want.
pub(crate) struct RoomVersion {
/// Whether `m.room.create` state events have a `creator` property.
///
/// The `creator` property is always equivalent to the event `sender`, and
/// was [removed][spec] in v11.
///
/// [spec]: https://spec.matrix.org/v1.11/rooms/v11/#remove-the-creator-property-of-mroomcreate-events
pub(crate) create_event_creator_prop: bool,
/// Whether the `redacts` property of `m.room.redaction` events is a
/// property in the event `content` or a top-level property of the event.
///
/// This property was [moved][spec] from top-level to `content` in v11.
///
/// [spec]: https://spec.matrix.org/v1.11/rooms/v11/#moving-the-redacts-property-of-mroomredaction-events-to-a-content-property
pub(crate) redaction_event_redacts_in_content: bool,
}
// Allowed so that we can use struct-update syntax for incremental changes
// between versions even when all fields change.
#[allow(clippy::needless_update)]
mod known_versions {
use super::RoomVersion;
pub(super) const V6: RoomVersion = RoomVersion {
create_event_creator_prop: true,
redaction_event_redacts_in_content: false,
};
pub(super) const V7: RoomVersion = RoomVersion {
..V6
};
pub(super) const V8: RoomVersion = RoomVersion {
..V7
};
pub(super) const V9: RoomVersion = RoomVersion {
..V8
};
pub(super) const V10: RoomVersion = RoomVersion {
..V9
};
pub(super) const V11: RoomVersion = RoomVersion {
create_event_creator_prop: false,
redaction_event_redacts_in_content: true,
..V10
};
}
/// May return an error for unsupported room versions.
impl TryFrom<&RoomVersionId> for RoomVersion {
type Error = Error;
fn try_from(version: &RoomVersionId) -> Result<RoomVersion, Error> {
match version {
RoomVersionId::V6 => Ok(known_versions::V6),
RoomVersionId::V7 => Ok(known_versions::V7),
RoomVersionId::V8 => Ok(known_versions::V8),
RoomVersionId::V9 => Ok(known_versions::V9),
RoomVersionId::V10 => Ok(known_versions::V10),
RoomVersionId::V11 => Ok(known_versions::V11),
_ => Err(Error::UnsupportedRoomVersion(version.clone())),
}
}
}