events: Generate structs without relation for events that can be replaced
This commit is contained in:
parent
6ec7fc09ea
commit
ec853e968a
@ -36,7 +36,7 @@ use super::{
|
||||
/// [`MessageType::Audio`]: super::room::message::MessageType::Audio
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_event(type = "m.audio", kind = MessageLike)]
|
||||
#[ruma_event(type = "m.audio", kind = MessageLike, without_relation)]
|
||||
pub struct AudioEventContent {
|
||||
/// The text representation of the message.
|
||||
#[serde(flatten)]
|
||||
|
@ -25,7 +25,7 @@ use super::{
|
||||
/// [`MessageType::Emote`]: super::room::message::MessageType::Emote
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_event(type = "m.emote", kind = MessageLike)]
|
||||
#[ruma_event(type = "m.emote", kind = MessageLike, without_relation)]
|
||||
pub struct EmoteEventContent {
|
||||
/// The message's text content.
|
||||
#[serde(flatten)]
|
||||
|
@ -34,7 +34,7 @@ use crate::{serde::Base64, OwnedMxcUri};
|
||||
/// [`MessageType::File`]: super::room::message::MessageType::File
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_event(type = "m.file", kind = MessageLike)]
|
||||
#[ruma_event(type = "m.file", kind = MessageLike, without_relation)]
|
||||
pub struct FileEventContent {
|
||||
/// The text representation of the message.
|
||||
#[serde(flatten)]
|
||||
|
@ -31,7 +31,7 @@ use crate::OwnedMxcUri;
|
||||
/// [`MessageType::Image`]: super::room::message::MessageType::Image
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_event(type = "m.image", kind = MessageLike)]
|
||||
#[ruma_event(type = "m.image", kind = MessageLike, without_relation)]
|
||||
pub struct ImageEventContent {
|
||||
/// The text representation of the message.
|
||||
#[serde(flatten)]
|
||||
|
@ -29,7 +29,7 @@ use crate::{MilliSecondsSinceUnixEpoch, PrivOwnedStr};
|
||||
/// [`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)]
|
||||
#[ruma_event(type = "m.location", kind = MessageLike, without_relation)]
|
||||
pub struct LocationEventContent {
|
||||
/// The text representation of the message.
|
||||
#[serde(flatten)]
|
||||
|
@ -81,7 +81,7 @@ use super::room::message::{
|
||||
/// [`MessageType::Text`]: super::room::message::MessageType::Text
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_event(type = "m.message", kind = MessageLike)]
|
||||
#[ruma_event(type = "m.message", kind = MessageLike, without_relation)]
|
||||
pub struct MessageEventContent {
|
||||
/// The message's text content.
|
||||
#[serde(flatten)]
|
||||
|
@ -25,7 +25,7 @@ use super::{
|
||||
/// [`MessageType::Notice`]: super::room::message::MessageType::Notice
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_event(type = "m.notice", kind = MessageLike)]
|
||||
#[ruma_event(type = "m.notice", kind = MessageLike, without_relation)]
|
||||
pub struct NoticeEventContent {
|
||||
/// The message's text content.
|
||||
#[serde(flatten)]
|
||||
|
@ -457,6 +457,12 @@ impl From<MessageType> for RoomMessageEventContent {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RoomMessageEventContent> for MessageType {
|
||||
fn from(content: RoomMessageEventContent) -> Self {
|
||||
content.msgtype
|
||||
}
|
||||
}
|
||||
|
||||
/// Message event relationship.
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(clippy::manual_non_exhaustive)]
|
||||
|
@ -32,7 +32,7 @@ use super::{
|
||||
/// [`MessageType::Video`]: super::room::message::MessageType::Video
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_event(type = "m.video", kind = MessageLike)]
|
||||
#[ruma_event(type = "m.video", kind = MessageLike, without_relation)]
|
||||
pub struct VideoEventContent {
|
||||
/// The text representation of the message.
|
||||
#[serde(flatten)]
|
||||
|
@ -29,7 +29,7 @@ use super::{
|
||||
/// [`MessageType::Audio`]: super::room::message::MessageType::Audio
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_event(type = "m.voice", kind = MessageLike)]
|
||||
#[ruma_event(type = "m.voice", kind = MessageLike, without_relation)]
|
||||
pub struct VoiceEventContent {
|
||||
/// The text representation of the message.
|
||||
#[serde(flatten)]
|
||||
|
@ -5,4 +5,6 @@ fn ui() {
|
||||
t.compile_fail("tests/events/ui/02-no-event-type.rs");
|
||||
t.compile_fail("tests/events/ui/03-invalid-event-type.rs");
|
||||
t.pass("tests/events/ui/10-content-wildcard.rs");
|
||||
t.pass("tests/events/ui/11-content-without-relation-sanity-check.rs");
|
||||
t.compile_fail("tests/events/ui/12-no-relates_to.rs");
|
||||
}
|
||||
|
@ -25,3 +25,4 @@ mod stripped;
|
||||
mod to_device;
|
||||
mod video;
|
||||
mod voice;
|
||||
mod without_relation;
|
||||
|
@ -6,7 +6,7 @@ error: no event type attribute found, add `#[ruma_event(type = "any.room.event",
|
||||
|
|
||||
= note: this error originates in the derive macro `EventContent` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: expected one of: `type`, `kind`, `custom_redacted`, `state_key_type`, `unsigned_type`, `alias`
|
||||
error: expected one of: `type`, `kind`, `custom_redacted`, `state_key_type`, `unsigned_type`, `alias`, `without_relation`
|
||||
--> tests/events/ui/03-invalid-event-type.rs:11:14
|
||||
|
|
||||
11 | #[ruma_event(event = "m.macro.test", kind = State)]
|
||||
|
@ -0,0 +1,11 @@
|
||||
use ruma_macros::EventContent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
||||
#[ruma_event(type = "m.macro.test", kind = MessageLike, without_relation)]
|
||||
pub struct MacroTestContent {
|
||||
pub url: String,
|
||||
pub relates_to: Option<String>,
|
||||
}
|
||||
|
||||
fn main() {}
|
10
crates/ruma-common/tests/events/ui/12-no-relates_to.rs
Normal file
10
crates/ruma-common/tests/events/ui/12-no-relates_to.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use ruma_macros::EventContent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
||||
#[ruma_event(type = "m.macro.test", kind = MessageLike, without_relation)]
|
||||
pub struct MacroTestContent {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,7 @@
|
||||
error: `without_relation` can only be used on events with a `relates_to` field
|
||||
--> tests/events/ui/12-no-relates_to.rs:4:48
|
||||
|
|
||||
4 | #[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `EventContent` (in Nightly builds, run with -Z macro-backtrace for more info)
|
70
crates/ruma-common/tests/events/without_relation.rs
Normal file
70
crates/ruma-common/tests/events/without_relation.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use assert_matches::assert_matches;
|
||||
use ruma_common::{
|
||||
event_id,
|
||||
events::room::message::{
|
||||
InReplyTo, MessageType, Relation, RoomMessageEventContent,
|
||||
RoomMessageEventContentWithoutRelation,
|
||||
},
|
||||
};
|
||||
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
||||
|
||||
#[test]
|
||||
fn serialize_room_message_content_without_relation() {
|
||||
let mut content = RoomMessageEventContent::text_plain("Hello, world!");
|
||||
content.relates_to =
|
||||
Some(Relation::Reply { in_reply_to: InReplyTo::new(event_id!("$eventId").to_owned()) });
|
||||
let without_relation = RoomMessageEventContentWithoutRelation::from(content);
|
||||
|
||||
#[cfg(not(feature = "unstable-msc3246"))]
|
||||
assert_eq!(
|
||||
to_json_value(&without_relation).unwrap(),
|
||||
json!({
|
||||
"body": "Hello, world!",
|
||||
"msgtype": "m.text",
|
||||
})
|
||||
);
|
||||
|
||||
#[cfg(feature = "unstable-msc3246")]
|
||||
assert_eq!(
|
||||
to_json_value(&without_relation).unwrap(),
|
||||
json!({
|
||||
"body": "Hello, world!",
|
||||
"msgtype": "m.text",
|
||||
"org.matrix.msc1767.text": "Hello, world!",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_room_message_content_without_relation() {
|
||||
let json_data = json!({
|
||||
"body": "Hello, world!",
|
||||
"msgtype": "m.text",
|
||||
});
|
||||
|
||||
let text = assert_matches!(
|
||||
from_json_value::<RoomMessageEventContentWithoutRelation>(json_data),
|
||||
Ok(RoomMessageEventContentWithoutRelation::Text(text)) => text
|
||||
);
|
||||
assert_eq!(text.body, "Hello, world!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_room_message_content_without_relation_to_full() {
|
||||
let mut content = RoomMessageEventContent::text_plain("Hello, world!");
|
||||
content.relates_to =
|
||||
Some(Relation::Reply { in_reply_to: InReplyTo::new(event_id!("$eventId").to_owned()) });
|
||||
let new_content =
|
||||
RoomMessageEventContent::from(RoomMessageEventContentWithoutRelation::from(content));
|
||||
|
||||
let (text, relates_to) = assert_matches!(
|
||||
new_content,
|
||||
RoomMessageEventContent {
|
||||
msgtype: MessageType::Text(text),
|
||||
relates_to,
|
||||
..
|
||||
} => (text, relates_to)
|
||||
);
|
||||
assert_eq!(text.body, "Hello, world!");
|
||||
assert_matches!(relates_to, None);
|
||||
}
|
@ -28,6 +28,8 @@ mod kw {
|
||||
syn::custom_keyword!(unsigned_type);
|
||||
// Another type string accepted for deserialization.
|
||||
syn::custom_keyword!(alias);
|
||||
// The content has a form without relation.
|
||||
syn::custom_keyword!(without_relation);
|
||||
}
|
||||
|
||||
/// Parses struct attributes for `*EventContent` derives.
|
||||
@ -49,6 +51,9 @@ enum EventStructMeta {
|
||||
|
||||
/// Variant that holds alternate event type accepted for deserialization.
|
||||
Alias(LitStr),
|
||||
|
||||
/// This attribute signals that a form without relation should be generated.
|
||||
WithoutRelation,
|
||||
}
|
||||
|
||||
impl EventStructMeta {
|
||||
@ -114,6 +119,9 @@ impl Parse for EventStructMeta {
|
||||
let _: kw::alias = input.parse()?;
|
||||
let _: Token![=] = input.parse()?;
|
||||
input.parse().map(EventStructMeta::Alias)
|
||||
} else if lookahead.peek(kw::without_relation) {
|
||||
let _: kw::without_relation = input.parse()?;
|
||||
Ok(EventStructMeta::WithoutRelation)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
@ -174,6 +182,10 @@ impl MetaAttrs {
|
||||
fn get_aliases(&self) -> impl Iterator<Item = &LitStr> {
|
||||
self.0.iter().filter_map(|a| a.get_alias())
|
||||
}
|
||||
|
||||
fn has_without_relation(&self) -> bool {
|
||||
self.0.iter().any(|a| matches!(*a, EventStructMeta::WithoutRelation))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for MetaAttrs {
|
||||
@ -320,6 +332,22 @@ pub fn expand_event_content(
|
||||
.unwrap_or_else(syn::Error::into_compile_error)
|
||||
});
|
||||
|
||||
let without_relations: Vec<_> =
|
||||
content_attr.iter().filter(|attrs| attrs.has_without_relation()).collect();
|
||||
let event_content_without_relation = match without_relations.as_slice() {
|
||||
[] => None,
|
||||
[_] => Some(
|
||||
generate_event_content_without_relation(ident, fields.clone(), ruma_common)
|
||||
.unwrap_or_else(syn::Error::into_compile_error),
|
||||
),
|
||||
_ => {
|
||||
return Err(syn::Error::new(
|
||||
Span::call_site(),
|
||||
"multiple without_relation attributes found, there can only be one",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let event_content_impl = generate_event_content_impl(
|
||||
ident,
|
||||
fields,
|
||||
@ -340,6 +368,7 @@ pub fn expand_event_content(
|
||||
|
||||
Ok(quote! {
|
||||
#redacted_event_content
|
||||
#event_content_without_relation
|
||||
#event_content_impl
|
||||
#static_event_content_impl
|
||||
#type_aliases
|
||||
@ -506,6 +535,70 @@ fn generate_redacted_event_content<'a>(
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_event_content_without_relation<'a>(
|
||||
ident: &Ident,
|
||||
fields: impl Iterator<Item = &'a Field>,
|
||||
ruma_common: &TokenStream,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let serde = quote! { #ruma_common::exports::serde };
|
||||
|
||||
let type_doc = format!(
|
||||
"Form of [`{ident}`] without relation.\n\n\
|
||||
To construct this type, construct a [`{ident}`] and then use one of its `::from() / .into()` methods."
|
||||
);
|
||||
let without_relation_ident = format_ident!("{ident}WithoutRelation");
|
||||
|
||||
let with_relation_fn_doc =
|
||||
format!("Transform `self` into a [`{ident}`] with the given relation.");
|
||||
|
||||
let (relates_to, other_fields) = fields.partition::<Vec<_>, _>(|f| {
|
||||
f.ident.as_ref().filter(|ident| *ident == "relates_to").is_some()
|
||||
});
|
||||
|
||||
let relates_to_type = relates_to.into_iter().next().map(|f| &f.ty).ok_or_else(|| {
|
||||
syn::Error::new(
|
||||
Span::call_site(),
|
||||
"`without_relation` can only be used on events with a `relates_to` field",
|
||||
)
|
||||
})?;
|
||||
|
||||
let without_relation_fields = other_fields.iter().flat_map(|f| &f.ident).collect::<Vec<_>>();
|
||||
let without_relation_struct = if other_fields.is_empty() {
|
||||
quote! { ; }
|
||||
} else {
|
||||
quote! {
|
||||
{ #( #other_fields, )* }
|
||||
}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#[allow(unused_qualifications)]
|
||||
#[automatically_derived]
|
||||
impl ::std::convert::From<#ident> for #without_relation_ident {
|
||||
fn from(c: #ident) -> Self {
|
||||
Self {
|
||||
#( #without_relation_fields: c.#without_relation_fields, )*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc = #type_doc]
|
||||
#[derive(Clone, Debug, #serde::Deserialize, #serde::Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct #without_relation_ident #without_relation_struct
|
||||
|
||||
impl #without_relation_ident {
|
||||
#[doc = #with_relation_fn_doc]
|
||||
pub fn with_relation(self, relates_to: #relates_to_type) -> #ident {
|
||||
#ident {
|
||||
#( #without_relation_fields: self.#without_relation_fields, )*
|
||||
relates_to,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_event_type_aliases(
|
||||
event_kind: EventKind,
|
||||
ident: &Ident,
|
||||
|
Loading…
x
Reference in New Issue
Block a user