events: Add support for transitional extensible location messages
According to MSC3488
This commit is contained in:
parent
c6d11c78a7
commit
aba6328d1c
@ -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.
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user