events: Rework Relation serde

Relation types now implement `Deserialize`, `Serialize`.
This commit is contained in:
Jonas Platte 2021-08-21 15:20:15 +02:00
parent 06f8b81ace
commit a81880c68f
No known key found for this signature in database
GPG Key ID: CC154DE0E30B7C67
6 changed files with 118 additions and 124 deletions

View File

@ -3,6 +3,12 @@
Improvements: Improvements:
* Add `From` implementations for event and event content enums * Add `From` implementations for event and event content enums
* It's now an error for a `room::message::Relation` to be `Replaces` without
there being `new_content`
* Previously, this used to set the relation to `None`
* Unsupported relations are now deserialized to `relates_to: Some(_)` instead of
`None`
* It's not possible to inspect the inner value though
# 0.24.4 # 0.24.4

View File

@ -33,7 +33,7 @@ pub struct EncryptedEventContent {
/// Information about related messages for [rich replies]. /// Information about related messages for [rich replies].
/// ///
/// [rich replies]: https://matrix.org/docs/spec/client_server/r0.6.1#rich-replies /// [rich replies]: https://matrix.org/docs/spec/client_server/r0.6.1#rich-replies
#[serde(flatten, with = "relation_serde", skip_serializing_if = "Option::is_none")] #[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>, pub relates_to: Option<Relation>,
} }
@ -91,6 +91,7 @@ pub enum EncryptedEventScheme {
/// ///
/// Outside of the encrypted payload to support server aggregation. /// Outside of the encrypted payload to support server aggregation.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[allow(clippy::manual_non_exhaustive)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub enum Relation { pub enum Relation {
/// An `m.in_reply_to` relation indicating that the event is a reply to another event. /// An `m.in_reply_to` relation indicating that the event is a reply to another event.
@ -113,6 +114,9 @@ pub enum Relation {
#[cfg(feature = "unstable-pre-spec")] #[cfg(feature = "unstable-pre-spec")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
Annotation(Annotation), Annotation(Annotation),
#[doc(hidden)]
_Custom,
} }
/// The event this relation belongs to replaces another event. /// The event this relation belongs to replaces another event.
@ -263,6 +267,7 @@ impl From<message::Relation> for Relation {
message::Relation::Replacement(re) => { message::Relation::Replacement(re) => {
Self::Replacement(Replacement { event_id: re.event_id }) Self::Replacement(Replacement { event_id: re.event_id })
} }
message::Relation::_Custom => Self::_Custom,
} }
} }
} }

View File

@ -1,77 +1,69 @@
use serde::{ser::SerializeStruct as _, Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "unstable-pre-spec")] #[cfg(feature = "unstable-pre-spec")]
use super::{Annotation, Reference, Replacement}; use super::{Annotation, Reference, Replacement};
use super::{InReplyTo, Relation}; use super::{InReplyTo, Relation};
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Relation>, D::Error> impl<'de> Deserialize<'de> for Relation {
where fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
D: Deserializer<'de>, where
{ D: Deserializer<'de>,
fn convert_relation(ev: EventWithRelatesToJsonRepr) -> Option<Relation> { {
if let Some(in_reply_to) = ev.relates_to.in_reply_to { fn convert_relation(ev: EventWithRelatesToJsonRepr) -> Relation {
return Some(Relation::Reply { in_reply_to }); if let Some(in_reply_to) = ev.relates_to.in_reply_to {
return Relation::Reply { in_reply_to };
}
#[cfg(feature = "unstable-pre-spec")]
if let Some(relation) = ev.relates_to.relation {
return match relation {
RelationJsonRepr::Annotation(a) => Relation::Annotation(a),
RelationJsonRepr::Reference(r) => Relation::Reference(r),
RelationJsonRepr::Replacement(Replacement { event_id }) => {
Relation::Replacement(Replacement { event_id })
}
// FIXME: Maybe we should log this, though at this point we don't even have
// access to the rel_type of the unknown relation.
RelationJsonRepr::Unknown => Relation::_Custom,
};
}
Relation::_Custom
} }
#[cfg(feature = "unstable-pre-spec")] EventWithRelatesToJsonRepr::deserialize(deserializer).map(convert_relation)
if let Some(relation) = ev.relates_to.relation {
let relation = match relation {
RelationJsonRepr::Annotation(a) => Relation::Annotation(a),
RelationJsonRepr::Reference(r) => Relation::Reference(r),
RelationJsonRepr::Replacement(Replacement { event_id }) => {
Relation::Replacement(Replacement { event_id })
}
// FIXME: Maybe we should log this, though at this point we don't even have access
// to the rel_type of the unknown relation.
RelationJsonRepr::Unknown => return None,
};
return Some(relation);
}
None
} }
EventWithRelatesToJsonRepr::deserialize(deserializer).map(convert_relation)
} }
pub fn serialize<S>(relation: &Option<Relation>, serializer: S) -> Result<S::Ok, S::Error> impl Serialize for Relation {
where fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
S: Serializer, where
{ S: Serializer,
let relation = match relation { {
Some(rel) => rel, let relates_to = match self {
// FIXME: If this crate ends up depending on tracing, emit a warning here. #[cfg(feature = "unstable-pre-spec")]
// This code path should not be reachable due to the skip_serializing_if serde attribute Relation::Annotation(r) => RelatesToJsonRepr {
// that should be applied together with `with = "relation_serde"`. relation: Some(RelationJsonRepr::Annotation(r.clone())),
None => return serializer.serialize_struct("NoRelation", 0)?.end(), ..Default::default()
}; },
#[cfg(feature = "unstable-pre-spec")]
let json_repr = match relation { Relation::Reference(r) => RelatesToJsonRepr {
#[cfg(feature = "unstable-pre-spec")] relation: Some(RelationJsonRepr::Reference(r.clone())),
Relation::Annotation(r) => EventWithRelatesToJsonRepr::new(RelatesToJsonRepr { ..Default::default()
relation: Some(RelationJsonRepr::Annotation(r.clone())), },
..Default::default() #[cfg(feature = "unstable-pre-spec")]
}), Relation::Replacement(r) => RelatesToJsonRepr {
#[cfg(feature = "unstable-pre-spec")]
Relation::Reference(r) => EventWithRelatesToJsonRepr::new(RelatesToJsonRepr {
relation: Some(RelationJsonRepr::Reference(r.clone())),
..Default::default()
}),
#[cfg(feature = "unstable-pre-spec")]
Relation::Replacement(r) => EventWithRelatesToJsonRepr {
relates_to: RelatesToJsonRepr {
relation: Some(RelationJsonRepr::Replacement(r.clone())), relation: Some(RelationJsonRepr::Replacement(r.clone())),
..Default::default() ..Default::default()
}, },
}, Relation::Reply { in_reply_to } => {
Relation::Reply { in_reply_to } => EventWithRelatesToJsonRepr::new(RelatesToJsonRepr { RelatesToJsonRepr { in_reply_to: Some(in_reply_to.clone()), ..Default::default() }
in_reply_to: Some(in_reply_to.clone()), }
..Default::default() Relation::_Custom => RelatesToJsonRepr::default(),
}), };
};
json_repr.serialize(serializer) EventWithRelatesToJsonRepr { relates_to }.serialize(serializer)
}
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
@ -80,12 +72,6 @@ struct EventWithRelatesToJsonRepr {
relates_to: RelatesToJsonRepr, relates_to: RelatesToJsonRepr,
} }
impl EventWithRelatesToJsonRepr {
fn new(relates_to: RelatesToJsonRepr) -> Self {
Self { relates_to }
}
}
/// Enum modeling the different ways relationships can be expressed in a `m.relates_to` field of an /// Enum modeling the different ways relationships can be expressed in a `m.relates_to` field of an
/// event. /// event.
#[derive(Default, Deserialize, Serialize)] #[derive(Default, Deserialize, Serialize)]

View File

@ -41,7 +41,7 @@ pub struct MessageEventContent {
/// Information about related messages for [rich replies]. /// Information about related messages for [rich replies].
/// ///
/// [rich replies]: https://matrix.org/docs/spec/client_server/r0.6.1#rich-replies /// [rich replies]: https://matrix.org/docs/spec/client_server/r0.6.1#rich-replies
#[serde(flatten, with = "relation_serde", skip_serializing_if = "Option::is_none")] #[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>, pub relates_to: Option<Relation>,
} }
@ -265,6 +265,7 @@ impl From<MessageType> for MessageEventContent {
/// ///
/// Currently used for replies and editing (message replacement). /// Currently used for replies and editing (message replacement).
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[allow(clippy::manual_non_exhaustive)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub enum Relation { pub enum Relation {
/// An `m.in_reply_to` relation indicating that the event is a reply to another event. /// An `m.in_reply_to` relation indicating that the event is a reply to another event.
@ -277,6 +278,9 @@ pub enum Relation {
#[cfg(feature = "unstable-pre-spec")] #[cfg(feature = "unstable-pre-spec")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
Replacement(Replacement), Replacement(Replacement),
#[doc(hidden)]
_Custom,
} }
/// Information about the event a "rich reply" is replying to. /// Information about the event a "rich reply" is replying to.

View File

@ -3,10 +3,8 @@
use serde::{de, Deserialize}; use serde::{de, Deserialize};
use serde_json::value::RawValue as RawJsonValue; use serde_json::value::RawValue as RawJsonValue;
use crate::{ use super::{MessageEventContent, MessageType, Relation};
from_raw_json_value, use crate::from_raw_json_value;
room::message::{MessageEventContent, MessageType},
};
impl<'de> Deserialize<'de> for MessageEventContent { impl<'de> Deserialize<'de> for MessageEventContent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@ -15,10 +13,10 @@ impl<'de> Deserialize<'de> for MessageEventContent {
{ {
let json = Box::<RawJsonValue>::deserialize(deserializer)?; let json = Box::<RawJsonValue>::deserialize(deserializer)?;
let mut deserializer = serde_json::Deserializer::from_str(json.get()); let mut deserializer = serde_json::Deserializer::from_str(json.get());
let relation = let relates_to =
super::relation_serde::deserialize(&mut deserializer).map_err(de::Error::custom)?; Option::<Relation>::deserialize(&mut deserializer).map_err(de::Error::custom)?;
Ok(Self { msgtype: from_raw_json_value(&json)?, relates_to: relation }) Ok(Self { msgtype: from_raw_json_value(&json)?, relates_to })
} }
} }

View File

@ -1,6 +1,6 @@
#[cfg(feature = "unstable-pre-spec")] #[cfg(feature = "unstable-pre-spec")]
use ruma_identifiers::EventId; use ruma_identifiers::EventId;
use serde::{ser::SerializeStruct as _, Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "unstable-pre-spec")] #[cfg(feature = "unstable-pre-spec")]
use super::Replacement; use super::Replacement;
@ -8,71 +8,66 @@ use super::{InReplyTo, Relation};
#[cfg(feature = "unstable-pre-spec")] #[cfg(feature = "unstable-pre-spec")]
use crate::room::message::MessageEventContent; use crate::room::message::MessageEventContent;
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Relation>, D::Error> impl<'de> Deserialize<'de> for Relation {
where fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
D: Deserializer<'de>, where
{ D: Deserializer<'de>,
fn convert_relation(ev: EventWithRelatesToJsonRepr) -> Option<Relation> { {
let ev = EventWithRelatesToJsonRepr::deserialize(deserializer)?;
if let Some(in_reply_to) = ev.relates_to.in_reply_to { if let Some(in_reply_to) = ev.relates_to.in_reply_to {
return Some(Relation::Reply { in_reply_to }); return Ok(Relation::Reply { in_reply_to });
} }
#[cfg(feature = "unstable-pre-spec")] #[cfg(feature = "unstable-pre-spec")]
if let Some(relation) = ev.relates_to.relation { if let Some(relation) = ev.relates_to.relation {
let relation = match relation { return Ok(match relation {
RelationJsonRepr::Replacement(ReplacementJsonRepr { event_id }) => { RelationJsonRepr::Replacement(ReplacementJsonRepr { event_id }) => {
let new_content = ev.new_content?; let new_content = ev
.new_content
.ok_or_else(|| serde::de::Error::missing_field("m.new_content"))?;
Relation::Replacement(Replacement { event_id, new_content }) Relation::Replacement(Replacement { event_id, new_content })
} }
// FIXME: Maybe we should log this, though at this point we don't even have access // FIXME: Maybe we should log this, though at this point we don't even have
// to the rel_type of the unknown relation. // access to the rel_type of the unknown relation.
RelationJsonRepr::Unknown => return None, RelationJsonRepr::Unknown => Relation::_Custom,
}; });
return Some(relation);
} }
None Ok(Relation::_Custom)
} }
EventWithRelatesToJsonRepr::deserialize(deserializer).map(convert_relation)
} }
pub fn serialize<S>(relation: &Option<Relation>, serializer: S) -> Result<S::Ok, S::Error> impl Serialize for Relation {
where fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
S: Serializer, where
{ S: Serializer,
let relation = match relation { {
Some(rel) => rel, let json_repr = match self {
// FIXME: If this crate ends up depending on tracing, emit a warning here. Relation::Reply { in_reply_to } => EventWithRelatesToJsonRepr::new(RelatesToJsonRepr {
// This code path should not be reachable due to the skip_serializing_if serde attribute in_reply_to: Some(in_reply_to.clone()),
// that should be applied together with `with = "relation_serde"`. ..Default::default()
None => return serializer.serialize_struct("NoRelation", 0)?.end(), }),
}; #[cfg(feature = "unstable-pre-spec")]
Relation::Replacement(Replacement { event_id, new_content }) => {
let json_repr = match relation { EventWithRelatesToJsonRepr {
Relation::Reply { in_reply_to } => EventWithRelatesToJsonRepr::new(RelatesToJsonRepr { relates_to: RelatesToJsonRepr {
in_reply_to: Some(in_reply_to.clone()), relation: Some(RelationJsonRepr::Replacement(ReplacementJsonRepr {
..Default::default() event_id: event_id.clone(),
}), })),
#[cfg(feature = "unstable-pre-spec")] ..Default::default()
Relation::Replacement(Replacement { event_id, new_content }) => { },
EventWithRelatesToJsonRepr { new_content: Some(new_content.clone()),
relates_to: RelatesToJsonRepr { }
relation: Some(RelationJsonRepr::Replacement(ReplacementJsonRepr {
event_id: event_id.clone(),
})),
..Default::default()
},
new_content: Some(new_content.clone()),
} }
} Relation::_Custom => EventWithRelatesToJsonRepr::default(),
}; };
json_repr.serialize(serializer) json_repr.serialize(serializer)
}
} }
#[derive(Deserialize, Serialize)] #[derive(Default, Deserialize, Serialize)]
struct EventWithRelatesToJsonRepr { struct EventWithRelatesToJsonRepr {
#[serde(rename = "m.relates_to", default, skip_serializing_if = "RelatesToJsonRepr::is_empty")] #[serde(rename = "m.relates_to", default, skip_serializing_if = "RelatesToJsonRepr::is_empty")]
relates_to: RelatesToJsonRepr, relates_to: RelatesToJsonRepr,