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 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
fn convert_relation(ev: EventWithRelatesToJsonRepr) -> Option<Relation> { fn convert_relation(ev: EventWithRelatesToJsonRepr) -> Relation {
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 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 match relation {
RelationJsonRepr::Annotation(a) => Relation::Annotation(a), RelationJsonRepr::Annotation(a) => Relation::Annotation(a),
RelationJsonRepr::Reference(r) => Relation::Reference(r), RelationJsonRepr::Reference(r) => Relation::Reference(r),
RelationJsonRepr::Replacement(Replacement { event_id }) => { RelationJsonRepr::Replacement(Replacement { event_id }) => {
Relation::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 // 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 Relation::_Custom
} }
EventWithRelatesToJsonRepr::deserialize(deserializer).map(convert_relation) EventWithRelatesToJsonRepr::deserialize(deserializer).map(convert_relation)
} }
}
pub fn serialize<S>(relation: &Option<Relation>, serializer: S) -> Result<S::Ok, S::Error> impl Serialize for Relation {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
let relation = match relation { let relates_to = match self {
Some(rel) => rel,
// FIXME: If this crate ends up depending on tracing, emit a warning here.
// This code path should not be reachable due to the skip_serializing_if serde attribute
// that should be applied together with `with = "relation_serde"`.
None => return serializer.serialize_struct("NoRelation", 0)?.end(),
};
let json_repr = match relation {
#[cfg(feature = "unstable-pre-spec")] #[cfg(feature = "unstable-pre-spec")]
Relation::Annotation(r) => EventWithRelatesToJsonRepr::new(RelatesToJsonRepr { Relation::Annotation(r) => RelatesToJsonRepr {
relation: Some(RelationJsonRepr::Annotation(r.clone())), relation: Some(RelationJsonRepr::Annotation(r.clone())),
..Default::default() ..Default::default()
}), },
#[cfg(feature = "unstable-pre-spec")] #[cfg(feature = "unstable-pre-spec")]
Relation::Reference(r) => EventWithRelatesToJsonRepr::new(RelatesToJsonRepr { Relation::Reference(r) => RelatesToJsonRepr {
relation: Some(RelationJsonRepr::Reference(r.clone())), relation: Some(RelationJsonRepr::Reference(r.clone())),
..Default::default() ..Default::default()
}), },
#[cfg(feature = "unstable-pre-spec")] #[cfg(feature = "unstable-pre-spec")]
Relation::Replacement(r) => EventWithRelatesToJsonRepr { Relation::Replacement(r) => RelatesToJsonRepr {
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,49 +8,42 @@ 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 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, 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) impl Serialize for Relation {
} fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
pub fn serialize<S>(relation: &Option<Relation>, serializer: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
let relation = match relation { let json_repr = match self {
Some(rel) => rel,
// FIXME: If this crate ends up depending on tracing, emit a warning here.
// This code path should not be reachable due to the skip_serializing_if serde attribute
// that should be applied together with `with = "relation_serde"`.
None => return serializer.serialize_struct("NoRelation", 0)?.end(),
};
let json_repr = match relation {
Relation::Reply { in_reply_to } => EventWithRelatesToJsonRepr::new(RelatesToJsonRepr { Relation::Reply { in_reply_to } => EventWithRelatesToJsonRepr::new(RelatesToJsonRepr {
in_reply_to: Some(in_reply_to.clone()), in_reply_to: Some(in_reply_to.clone()),
..Default::default() ..Default::default()
@ -67,12 +60,14 @@ where
new_content: Some(new_content.clone()), 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,