events: Add support for transitional extensible location messages

According to MSC3488
This commit is contained in:
Kévin Commaille 2022-03-26 16:05:42 +01:00 committed by Kévin Commaille
parent c6d11c78a7
commit aba6328d1c
4 changed files with 269 additions and 4 deletions

View File

@ -10,10 +10,25 @@ use serde::{Deserialize, Serialize};
mod zoomlevel_serde;
use super::{message::MessageContent, room::message::Relation};
use super::{
message::MessageContent,
room::message::{LocationMessageEventContent, Relation},
};
use crate::{MilliSecondsSinceUnixEpoch, PrivOwnedStr};
/// The payload for an extensible location message.
///
/// This is the new primary type introduced in [MSC3488] and should not be sent before the end of
/// the transition period. See the documentation of the [`message`] module for more information.
///
/// `LocationEventContent` can be converted to a [`RoomMessageEventContent`] with a
/// [`MessageType::Location`]. You can convert it back with
/// [`LocationEventContent::from_location_room_message()`].
///
/// [MSC3488]: https://github.com/matrix-org/matrix-spec-proposals/pull/3488
/// [`message`]: super::message
/// [`RoomMessageEventContent`]: super::room::message::RoomMessageEventContent
/// [`MessageType::Location`]: super::room::message::MessageType::Location
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.location", kind = MessageLike)]
@ -55,6 +70,22 @@ impl LocationEventContent {
pub fn with_message(message: MessageContent, location: LocationContent) -> Self {
Self { message, location, asset: Default::default(), ts: None, relates_to: None }
}
/// Create a new `LocationEventContent` from the given `LocationMessageEventContent` and
/// optional relation.
pub fn from_location_room_message(
content: LocationMessageEventContent,
relates_to: Option<Relation>,
) -> Self {
let LocationMessageEventContent { body, geo_uri, message, location, asset, ts, .. } =
content;
let message = message.unwrap_or_else(|| MessageContent::plain(body));
let location = location.unwrap_or_else(|| LocationContent::new(geo_uri));
let asset = asset.unwrap_or_default();
Self { message, location, asset, ts, relates_to }
}
}
/// Location content.

View File

@ -31,6 +31,11 @@ use crate::{
serde::{JsonObject, StringEnum},
DeviceId, EventId, MxcUri, PrivOwnedStr, UserId,
};
#[cfg(feature = "unstable-msc3488")]
use crate::{
events::location::{AssetContent, LocationContent, LocationEventContent},
MilliSecondsSinceUnixEpoch,
};
mod content_serde;
pub mod feedback;
@ -249,6 +254,20 @@ impl From<ImageEventContent> for RoomMessageEventContent {
}
}
#[cfg(feature = "unstable-msc3488")]
impl From<LocationEventContent> for RoomMessageEventContent {
fn from(content: LocationEventContent) -> Self {
let LocationEventContent { message, location, asset, ts, relates_to } = content;
Self {
msgtype: MessageType::Location(LocationMessageEventContent::from_extensible_content(
message, location, asset, ts,
)),
relates_to,
}
}
}
#[cfg(feature = "unstable-msc1767")]
impl From<MessageEventContent> for RoomMessageEventContent {
fn from(content: MessageEventContent) -> Self {
@ -1073,6 +1092,10 @@ impl ImageMessageEventContent {
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[serde(tag = "msgtype", rename = "m.location")]
#[cfg_attr(
feature = "unstable-msc3488",
serde(from = "content_serde::LocationMessageEventContentDeHelper")
)]
pub struct LocationMessageEventContent {
/// A description of the location e.g. "Big Ben, London, UK", or some kind of content
/// description for accessibility, e.g. "location attachment".
@ -1084,12 +1107,75 @@ pub struct LocationMessageEventContent {
/// Info about the location being represented.
#[serde(skip_serializing_if = "Option::is_none")]
pub info: Option<Box<LocationInfo>>,
/// Extensible-event text representation of the message.
///
/// If present, this should be preferred over the `body` field.
#[cfg(feature = "unstable-msc3488")]
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub message: Option<MessageContent>,
/// Extensible-event location info of the message.
///
/// If present, this should be preferred over the `geo_uri` field.
#[cfg(feature = "unstable-msc3488")]
#[serde(rename = "org.matrix.msc3488.location", skip_serializing_if = "Option::is_none")]
pub location: Option<LocationContent>,
/// Extensible-event asset this message refers to.
#[cfg(feature = "unstable-msc3488")]
#[serde(rename = "org.matrix.msc3488.asset", skip_serializing_if = "Option::is_none")]
pub asset: Option<AssetContent>,
/// Extensible-event timestamp this message refers to.
#[cfg(feature = "unstable-msc3488")]
#[serde(rename = "org.matrix.msc3488.ts", skip_serializing_if = "Option::is_none")]
pub ts: Option<MilliSecondsSinceUnixEpoch>,
}
impl LocationMessageEventContent {
/// Creates a new `RoomLocationMessageEventContent` with the given body and geo URI.
pub fn new(body: String, geo_uri: String) -> Self {
Self { body, geo_uri, info: None }
Self {
#[cfg(feature = "unstable-msc3488")]
message: Some(MessageContent::plain(body.clone())),
#[cfg(feature = "unstable-msc3488")]
location: Some(LocationContent::new(geo_uri.clone())),
#[cfg(feature = "unstable-msc3488")]
asset: None,
#[cfg(feature = "unstable-msc3488")]
ts: None,
body,
geo_uri,
info: None,
}
}
/// Create a new `LocationMessageEventContent` with the given message, location info, asset and
/// timestamp.
#[cfg(feature = "unstable-msc3488")]
pub fn from_extensible_content(
message: MessageContent,
location: LocationContent,
asset: AssetContent,
ts: Option<MilliSecondsSinceUnixEpoch>,
) -> Self {
let body = if let Some(body) = message.find_plain() {
body.to_owned()
} else {
message[0].body.clone()
};
let geo_uri = location.uri.clone();
Self {
message: Some(message),
location: Some(location),
asset: Some(asset),
ts,
body,
geo_uri,
info: None,
}
}
}

View File

@ -5,6 +5,11 @@ use serde_json::value::RawValue as RawJsonValue;
#[cfg(feature = "unstable-msc3245")]
use super::VoiceContent;
#[cfg(feature = "unstable-msc3488")]
use super::{
AssetContent, LocationContent, LocationInfo, LocationMessageEventContent,
MilliSecondsSinceUnixEpoch,
};
#[cfg(feature = "unstable-msc3246")]
use super::{AudioContent, AudioInfo, AudioMessageEventContent};
#[cfg(feature = "unstable-msc3551")]
@ -278,6 +283,77 @@ impl From<ImageMessageEventContentDeHelper> for ImageMessageEventContent {
}
}
/// Helper struct for deserializing `LocationMessageEventContent` with stable and unstable field
/// names.
///
/// It's not possible to use the `alias` attribute of serde because of
/// https://github.com/serde-rs/serde/issues/1504.
#[derive(Clone, Debug, Deserialize)]
#[cfg(feature = "unstable-msc3488")]
pub struct LocationMessageEventContentDeHelper {
/// A description of the location.
pub body: String,
/// A geo URI representing the location.
pub geo_uri: String,
/// Info about the location being represented.
#[serde(skip_serializing_if = "Option::is_none")]
pub info: Option<Box<LocationInfo>>,
/// Extensible-event text representation of the message.
#[serde(flatten)]
pub message: Option<MessageContent>,
/// Extensible-event location info of the message, with stable name.
#[serde(rename = "m.location")]
pub location_stable: Option<LocationContent>,
/// Extensible-event location info of the message, with unstable name.
#[serde(rename = "org.matrix.msc3488.location")]
pub location_unstable: Option<LocationContent>,
/// Extensible-event asset this message refers to, with stable name.
#[serde(rename = "m.asset")]
pub asset_stable: Option<AssetContent>,
/// Extensible-event asset this message refers to, with unstable name.
#[serde(rename = "org.matrix.msc3488.asset")]
pub asset_unstable: Option<AssetContent>,
/// Extensible-event timestamp this message refers to, with stable name.
#[serde(rename = "m.ts")]
pub ts_stable: Option<MilliSecondsSinceUnixEpoch>,
/// Extensible-event timestamp this message refers to, with unstable name.
#[serde(rename = "org.matrix.msc3488.ts")]
pub ts_unstable: Option<MilliSecondsSinceUnixEpoch>,
}
#[cfg(feature = "unstable-msc3488")]
impl From<LocationMessageEventContentDeHelper> for LocationMessageEventContent {
fn from(helper: LocationMessageEventContentDeHelper) -> Self {
let LocationMessageEventContentDeHelper {
body,
geo_uri,
info,
message,
location_stable,
location_unstable,
asset_stable,
asset_unstable,
ts_stable,
ts_unstable,
} = helper;
let location = location_stable.or(location_unstable);
let asset = asset_stable.or(asset_unstable);
let ts = ts_stable.or(ts_unstable);
Self { body, geo_uri, info, message, location, asset, ts }
}
}
/// Helper struct for deserializing `VideoMessageEventContent` with stable and unstable field names.
///
/// It's not possible to use the `alias` attribute of serde because of

View File

@ -11,7 +11,9 @@ use ruma_common::{
ZoomLevelError,
},
message::MessageContent,
room::message::{InReplyTo, Relation},
room::message::{
InReplyTo, LocationMessageEventContent, MessageType, Relation, RoomMessageEventContent,
},
AnyMessageLikeEvent, MessageLikeEvent, MessageLikeUnsigned,
},
room_id, user_id, MilliSecondsSinceUnixEpoch,
@ -105,7 +107,7 @@ fn event_serialization() {
#[test]
fn plain_content_deserialization() {
let json_data = json!({
"org.matrix.msc1767.text": "Alice was at geo:51.5008,0.1247;u=35",
"m.text": "Alice was at geo:51.5008,0.1247;u=35",
"m.location": {
"uri": "geo:51.5008,0.1247;u=35",
},
@ -228,3 +230,73 @@ fn message_event_deserialization() {
&& unsigned.is_empty()
);
}
#[test]
fn room_message_serialization() {
let message_event_content =
RoomMessageEventContent::new(MessageType::Location(LocationMessageEventContent::new(
"Alice was at geo:51.5008,0.1247;u=35".to_owned(),
"geo:51.5008,0.1247;u=35".to_owned(),
)));
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"body": "Alice was at geo:51.5008,0.1247;u=35",
"geo_uri": "geo:51.5008,0.1247;u=35",
"msgtype": "m.location",
"org.matrix.msc1767.text": "Alice was at geo:51.5008,0.1247;u=35",
"org.matrix.msc3488.location": {
"uri": "geo:51.5008,0.1247;u=35",
},
})
);
}
#[test]
fn room_message_stable_deserialization() {
let json_data = json!({
"body": "Alice was at geo:51.5008,0.1247;u=35",
"geo_uri": "geo:51.5008,0.1247;u=35",
"msgtype": "m.location",
"m.text": "Alice was at geo:51.5008,0.1247;u=35",
"m.location": {
"uri": "geo:51.5008,0.1247;u=35",
},
});
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
assert_matches!(event_content.msgtype, MessageType::Location(_));
if let MessageType::Location(content) = event_content.msgtype {
assert_eq!(content.body, "Alice was at geo:51.5008,0.1247;u=35");
assert_eq!(content.geo_uri, "geo:51.5008,0.1247;u=35");
let message = content.message.unwrap();
assert_eq!(message.len(), 1);
assert_eq!(message[0].body, "Alice was at geo:51.5008,0.1247;u=35");
assert_eq!(content.location.unwrap().uri, "geo:51.5008,0.1247;u=35");
}
}
#[test]
fn room_message_unstable_deserialization() {
let json_data = json!({
"body": "Alice was at geo:51.5008,0.1247;u=35",
"geo_uri": "geo:51.5008,0.1247;u=35",
"msgtype": "m.location",
"org.matrix.msc1767.text": "Alice was at geo:51.5008,0.1247;u=35",
"org.matrix.msc3488.location": {
"uri": "geo:51.5008,0.1247;u=35",
},
});
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
assert_matches!(event_content.msgtype, MessageType::Location(_));
if let MessageType::Location(content) = event_content.msgtype {
assert_eq!(content.body, "Alice was at geo:51.5008,0.1247;u=35");
assert_eq!(content.geo_uri, "geo:51.5008,0.1247;u=35");
let message = content.message.unwrap();
assert_eq!(message.len(), 1);
assert_eq!(message[0].body, "Alice was at geo:51.5008,0.1247;u=35");
assert_eq!(content.location.unwrap().uri, "geo:51.5008,0.1247;u=35");
}
}