events: Generate PossiblyRedacted type for original state events
Fix deserialization of redacted prev_content Can be overriden with the `custom_possibly_redacted` attribute Co-authored-by: Jonas Platte <jplatte@element.io>
This commit is contained in:
parent
0578e7af50
commit
cd74cdcc0e
@ -8,6 +8,7 @@ Bug fixes:
|
|||||||
and `events::secret` modules
|
and `events::secret` modules
|
||||||
* Fix deserialization of `RoomMessageEventContent` and `RoomEncryptedEventContent` when there
|
* Fix deserialization of `RoomMessageEventContent` and `RoomEncryptedEventContent` when there
|
||||||
is no relation
|
is no relation
|
||||||
|
* Fix deserialization of `StateUnsigned` when the `prev_content` is redacted
|
||||||
|
|
||||||
Breaking changes:
|
Breaking changes:
|
||||||
|
|
||||||
|
@ -72,6 +72,7 @@ impl StateEventContent for CustomStateEventContent {
|
|||||||
}
|
}
|
||||||
impl OriginalStateEventContent for CustomStateEventContent {
|
impl OriginalStateEventContent for CustomStateEventContent {
|
||||||
type Unsigned = StateUnsigned<Self>;
|
type Unsigned = StateUnsigned<Self>;
|
||||||
|
type PossiblyRedacted = Self;
|
||||||
}
|
}
|
||||||
impl RedactedStateEventContent for CustomStateEventContent {}
|
impl RedactedStateEventContent for CustomStateEventContent {}
|
||||||
|
|
||||||
|
@ -130,6 +130,9 @@ pub trait StateEventContent: EventContent<EventType = StateEventType> {
|
|||||||
pub trait OriginalStateEventContent: StateEventContent + RedactContent {
|
pub trait OriginalStateEventContent: StateEventContent + RedactContent {
|
||||||
/// The type of the event's `unsigned` field.
|
/// The type of the event's `unsigned` field.
|
||||||
type Unsigned: Clone + fmt::Debug + Default + CanBeEmpty + StateUnsignedFromParts;
|
type Unsigned: Clone + fmt::Debug + Default + CanBeEmpty + StateUnsignedFromParts;
|
||||||
|
|
||||||
|
/// The possibly redacted form of the event's content.
|
||||||
|
type PossiblyRedacted: StateEventContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Content of a redacted state event.
|
/// Content of a redacted state event.
|
||||||
|
@ -471,7 +471,7 @@ pub struct DecryptedMegolmV1Event<C: MessageLikeEventContent> {
|
|||||||
/// A non-redacted content also contains the `prev_content` from the unsigned event data.
|
/// A non-redacted content also contains the `prev_content` from the unsigned event data.
|
||||||
#[allow(clippy::exhaustive_enums)]
|
#[allow(clippy::exhaustive_enums)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum FullStateEventContent<C: StateEventContent + RedactContent>
|
pub enum FullStateEventContent<C: OriginalStateEventContent>
|
||||||
where
|
where
|
||||||
C::Redacted: RedactedStateEventContent,
|
C::Redacted: RedactedStateEventContent,
|
||||||
{
|
{
|
||||||
@ -481,14 +481,14 @@ where
|
|||||||
content: C,
|
content: C,
|
||||||
|
|
||||||
/// Previous content of the room state.
|
/// Previous content of the room state.
|
||||||
prev_content: Option<C>,
|
prev_content: Option<C::PossiblyRedacted>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Redacted content of the event.
|
/// Redacted content of the event.
|
||||||
Redacted(C::Redacted),
|
Redacted(C::Redacted),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: StateEventContent + RedactContent> FullStateEventContent<C>
|
impl<C: OriginalStateEventContent> FullStateEventContent<C>
|
||||||
where
|
where
|
||||||
C::Redacted: RedactedStateEventContent,
|
C::Redacted: RedactedStateEventContent,
|
||||||
{
|
{
|
||||||
|
@ -32,6 +32,28 @@ impl PolicyRuleEventContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The possibly redacted form of [`PolicyRuleEventContent`].
|
||||||
|
///
|
||||||
|
/// This type is used when it's not obvious whether the content is redacted or not.
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
|
pub struct PossiblyRedactedPolicyRuleEventContent {
|
||||||
|
/// The entity affected by this rule.
|
||||||
|
///
|
||||||
|
/// Glob characters `*` and `?` can be used to match zero or more characters or exactly one
|
||||||
|
/// character respectively.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub entity: Option<String>,
|
||||||
|
|
||||||
|
/// The suggested action to take.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub recommendation: Option<Recommendation>,
|
||||||
|
|
||||||
|
/// The human-readable description for the recommendation.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
/// The possible actions that can be taken.
|
/// The possible actions that can be taken.
|
||||||
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
|
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, StringEnum)]
|
#[derive(Clone, Debug, PartialEq, Eq, StringEnum)]
|
||||||
|
@ -4,17 +4,48 @@
|
|||||||
|
|
||||||
use ruma_macros::EventContent;
|
use ruma_macros::EventContent;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::value::RawValue as RawJsonValue;
|
||||||
|
|
||||||
use super::PolicyRuleEventContent;
|
use super::{PolicyRuleEventContent, PossiblyRedactedPolicyRuleEventContent};
|
||||||
|
use crate::events::{EventContent, StateEventContent, StateEventType};
|
||||||
|
|
||||||
/// The content of an `m.policy.rule.room` event.
|
/// The content of an `m.policy.rule.room` event.
|
||||||
///
|
///
|
||||||
/// This event type is used to apply rules to room entities.
|
/// This event type is used to apply rules to room entities.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
||||||
#[allow(clippy::exhaustive_structs)]
|
#[allow(clippy::exhaustive_structs)]
|
||||||
#[ruma_event(type = "m.policy.rule.room", kind = State, state_key_type = String)]
|
#[ruma_event(type = "m.policy.rule.room", kind = State, state_key_type = String, custom_possibly_redacted)]
|
||||||
pub struct PolicyRuleRoomEventContent(pub PolicyRuleEventContent);
|
pub struct PolicyRuleRoomEventContent(pub PolicyRuleEventContent);
|
||||||
|
|
||||||
|
/// The possibly redacted form of [`PolicyRuleRoomEventContent`].
|
||||||
|
///
|
||||||
|
/// This type is used when it's not obvious whether the content is redacted or not.
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[allow(clippy::exhaustive_structs)]
|
||||||
|
pub struct PossiblyRedactedPolicyRuleRoomEventContent(pub PossiblyRedactedPolicyRuleEventContent);
|
||||||
|
|
||||||
|
impl EventContent for PossiblyRedactedPolicyRuleRoomEventContent {
|
||||||
|
type EventType = StateEventType;
|
||||||
|
|
||||||
|
fn event_type(&self) -> Self::EventType {
|
||||||
|
StateEventType::PolicyRuleRoom
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_parts(event_type: &str, content: &RawJsonValue) -> serde_json::Result<Self> {
|
||||||
|
if event_type != "m.policy.rule.room" {
|
||||||
|
return Err(::serde::de::Error::custom(format!(
|
||||||
|
"expected event type `m.policy.rule.room`, found `{event_type}`",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_json::from_str(content.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateEventContent for PossiblyRedactedPolicyRuleRoomEventContent {
|
||||||
|
type StateKey = String;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
||||||
|
@ -4,13 +4,44 @@
|
|||||||
|
|
||||||
use ruma_macros::EventContent;
|
use ruma_macros::EventContent;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::value::RawValue as RawJsonValue;
|
||||||
|
|
||||||
use super::PolicyRuleEventContent;
|
use super::{PolicyRuleEventContent, PossiblyRedactedPolicyRuleEventContent};
|
||||||
|
use crate::events::{EventContent, StateEventContent, StateEventType};
|
||||||
|
|
||||||
/// The content of an `m.policy.rule.server` event.
|
/// The content of an `m.policy.rule.server` event.
|
||||||
///
|
///
|
||||||
/// This event type is used to apply rules to server entities.
|
/// This event type is used to apply rules to server entities.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
||||||
#[allow(clippy::exhaustive_structs)]
|
#[allow(clippy::exhaustive_structs)]
|
||||||
#[ruma_event(type = "m.policy.rule.server", kind = State, state_key_type = String)]
|
#[ruma_event(type = "m.policy.rule.server", kind = State, state_key_type = String, custom_possibly_redacted)]
|
||||||
pub struct PolicyRuleServerEventContent(pub PolicyRuleEventContent);
|
pub struct PolicyRuleServerEventContent(pub PolicyRuleEventContent);
|
||||||
|
|
||||||
|
/// The possibly redacted form of [`PolicyRuleServerEventContent`].
|
||||||
|
///
|
||||||
|
/// This type is used when it's not obvious whether the content is redacted or not.
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[allow(clippy::exhaustive_structs)]
|
||||||
|
pub struct PossiblyRedactedPolicyRuleServerEventContent(pub PossiblyRedactedPolicyRuleEventContent);
|
||||||
|
|
||||||
|
impl EventContent for PossiblyRedactedPolicyRuleServerEventContent {
|
||||||
|
type EventType = StateEventType;
|
||||||
|
|
||||||
|
fn event_type(&self) -> Self::EventType {
|
||||||
|
StateEventType::PolicyRuleServer
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_parts(event_type: &str, content: &RawJsonValue) -> serde_json::Result<Self> {
|
||||||
|
if event_type != "m.policy.rule.server" {
|
||||||
|
return Err(::serde::de::Error::custom(format!(
|
||||||
|
"expected event type `m.policy.rule.server`, found `{event_type}`",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_json::from_str(content.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateEventContent for PossiblyRedactedPolicyRuleServerEventContent {
|
||||||
|
type StateKey = String;
|
||||||
|
}
|
||||||
|
@ -4,13 +4,44 @@
|
|||||||
|
|
||||||
use ruma_macros::EventContent;
|
use ruma_macros::EventContent;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::value::RawValue as RawJsonValue;
|
||||||
|
|
||||||
use super::PolicyRuleEventContent;
|
use super::{PolicyRuleEventContent, PossiblyRedactedPolicyRuleEventContent};
|
||||||
|
use crate::events::{EventContent, StateEventContent, StateEventType};
|
||||||
|
|
||||||
/// The content of an `m.policy.rule.user` event.
|
/// The content of an `m.policy.rule.user` event.
|
||||||
///
|
///
|
||||||
/// This event type is used to apply rules to user entities.
|
/// This event type is used to apply rules to user entities.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
||||||
#[allow(clippy::exhaustive_structs)]
|
#[allow(clippy::exhaustive_structs)]
|
||||||
#[ruma_event(type = "m.policy.rule.user", kind = State, state_key_type = String)]
|
#[ruma_event(type = "m.policy.rule.user", kind = State, state_key_type = String, custom_possibly_redacted)]
|
||||||
pub struct PolicyRuleUserEventContent(pub PolicyRuleEventContent);
|
pub struct PolicyRuleUserEventContent(pub PolicyRuleEventContent);
|
||||||
|
|
||||||
|
/// The possibly redacted form of [`PolicyRuleUserEventContent`].
|
||||||
|
///
|
||||||
|
/// This type is used when it's not obvious whether the content is redacted or not.
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[allow(clippy::exhaustive_structs)]
|
||||||
|
pub struct PossiblyRedactedPolicyRuleUserEventContent(pub PossiblyRedactedPolicyRuleEventContent);
|
||||||
|
|
||||||
|
impl EventContent for PossiblyRedactedPolicyRuleUserEventContent {
|
||||||
|
type EventType = StateEventType;
|
||||||
|
|
||||||
|
fn event_type(&self) -> Self::EventType {
|
||||||
|
StateEventType::PolicyRuleUser
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_parts(event_type: &str, content: &RawJsonValue) -> serde_json::Result<Self> {
|
||||||
|
if event_type != "m.policy.rule.user" {
|
||||||
|
return Err(::serde::de::Error::custom(format!(
|
||||||
|
"expected event type `m.policy.rule.user`, found `{event_type}`",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_json::from_str(content.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateEventContent for PossiblyRedactedPolicyRuleUserEventContent {
|
||||||
|
type StateKey = String;
|
||||||
|
}
|
||||||
|
@ -53,6 +53,7 @@ pub use self::change::{Change, MembershipChange, MembershipDetails};
|
|||||||
state_key_type = OwnedUserId,
|
state_key_type = OwnedUserId,
|
||||||
unsigned_type = RoomMemberUnsigned,
|
unsigned_type = RoomMemberUnsigned,
|
||||||
custom_redacted,
|
custom_redacted,
|
||||||
|
custom_possibly_redacted,
|
||||||
)]
|
)]
|
||||||
pub struct RoomMemberEventContent {
|
pub struct RoomMemberEventContent {
|
||||||
/// The avatar URL for this user, if any.
|
/// The avatar URL for this user, if any.
|
||||||
@ -179,6 +180,11 @@ impl RedactContent for RoomMemberEventContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The possibly redacted form of [`RoomMemberEventContent`].
|
||||||
|
///
|
||||||
|
/// This type is used when it's not obvious whether the content is redacted or not.
|
||||||
|
pub type PossiblyRedactedRoomMemberEventContent = RoomMemberEventContent;
|
||||||
|
|
||||||
/// A member event that has been redacted.
|
/// A member event that has been redacted.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
@ -509,7 +515,7 @@ pub struct RoomMemberUnsigned {
|
|||||||
pub transaction_id: Option<OwnedTransactionId>,
|
pub transaction_id: Option<OwnedTransactionId>,
|
||||||
|
|
||||||
/// Optional previous content of the event.
|
/// Optional previous content of the event.
|
||||||
pub prev_content: Option<RoomMemberEventContent>,
|
pub prev_content: Option<PossiblyRedactedRoomMemberEventContent>,
|
||||||
|
|
||||||
/// State events to assist the receiver in identifying the room.
|
/// State events to assist the receiver in identifying the room.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -60,7 +60,7 @@ fn deserialize_aliases_with_prev_content() {
|
|||||||
assert_eq!(ev.sender, "@carl:example.com");
|
assert_eq!(ev.sender, "@carl:example.com");
|
||||||
|
|
||||||
let prev_content = ev.unsigned.prev_content.unwrap();
|
let prev_content = ev.unsigned.prev_content.unwrap();
|
||||||
assert_eq!(prev_content.aliases, vec![room_alias_id!("#inner:localhost")]);
|
assert_eq!(prev_content.aliases.unwrap(), vec![room_alias_id!("#inner:localhost")]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -78,7 +78,7 @@ fn deserialize_aliases_sync_with_room_id() {
|
|||||||
assert_eq!(ev.sender, "@carl:example.com");
|
assert_eq!(ev.sender, "@carl:example.com");
|
||||||
|
|
||||||
let prev_content = ev.unsigned.prev_content.unwrap();
|
let prev_content = ev.unsigned.prev_content.unwrap();
|
||||||
assert_eq!(prev_content.aliases, vec![room_alias_id!("#inner:localhost")]);
|
assert_eq!(prev_content.aliases.unwrap(), vec![room_alias_id!("#inner:localhost")]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -177,7 +177,7 @@ fn deserialize_full_event_convert_to_sync() {
|
|||||||
assert_eq!(sync_ev.event_id, "$h29iv0s8:example.com");
|
assert_eq!(sync_ev.event_id, "$h29iv0s8:example.com");
|
||||||
assert_eq!(sync_ev.origin_server_ts, MilliSecondsSinceUnixEpoch(uint!(1)));
|
assert_eq!(sync_ev.origin_server_ts, MilliSecondsSinceUnixEpoch(uint!(1)));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_ev.unsigned.prev_content.unwrap().aliases,
|
sync_ev.unsigned.prev_content.unwrap().aliases.unwrap(),
|
||||||
vec![room_alias_id!("#inner:localhost")]
|
vec![room_alias_id!("#inner:localhost")]
|
||||||
);
|
);
|
||||||
assert_eq!(sync_ev.sender, "@carl:example.com");
|
assert_eq!(sync_ev.sender, "@carl:example.com");
|
||||||
|
@ -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)
|
= 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`, `without_relation`
|
error: expected one of: `type`, `kind`, `custom_redacted`, `custom_possibly_redacted`, `state_key_type`, `unsigned_type`, `alias`, `without_relation`
|
||||||
--> tests/events/ui/03-invalid-event-type.rs:11:14
|
--> tests/events/ui/03-invalid-event-type.rs:11:14
|
||||||
|
|
|
|
||||||
11 | #[ruma_event(event = "m.macro.test", kind = State)]
|
11 | #[ruma_event(event = "m.macro.test", kind = State)]
|
||||||
|
@ -7,8 +7,9 @@ use proc_macro2::{Span, TokenStream};
|
|||||||
use quote::{format_ident, quote, ToTokens};
|
use quote::{format_ident, quote, ToTokens};
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
|
parse_quote,
|
||||||
punctuated::Punctuated,
|
punctuated::Punctuated,
|
||||||
DeriveInput, Field, Ident, LitStr, Token, Type,
|
DeriveInput, Field, Ident, LitStr, Meta, NestedMeta, Token, Type,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::util::m_prefix_name_to_type_name;
|
use crate::util::m_prefix_name_to_type_name;
|
||||||
@ -20,6 +21,8 @@ mod kw {
|
|||||||
syn::custom_keyword!(skip_redaction);
|
syn::custom_keyword!(skip_redaction);
|
||||||
// Do not emit any redacted event code.
|
// Do not emit any redacted event code.
|
||||||
syn::custom_keyword!(custom_redacted);
|
syn::custom_keyword!(custom_redacted);
|
||||||
|
// Do not emit any possibly redacted event code.
|
||||||
|
syn::custom_keyword!(custom_possibly_redacted);
|
||||||
// The kind of event content this is.
|
// The kind of event content this is.
|
||||||
syn::custom_keyword!(kind);
|
syn::custom_keyword!(kind);
|
||||||
syn::custom_keyword!(type_fragment);
|
syn::custom_keyword!(type_fragment);
|
||||||
@ -66,6 +69,7 @@ struct ContentMeta {
|
|||||||
event_type: Option<LitStr>,
|
event_type: Option<LitStr>,
|
||||||
event_kind: Option<EventKind>,
|
event_kind: Option<EventKind>,
|
||||||
custom_redacted: Option<kw::custom_redacted>,
|
custom_redacted: Option<kw::custom_redacted>,
|
||||||
|
custom_possibly_redacted: Option<kw::custom_possibly_redacted>,
|
||||||
state_key_type: Option<Box<Type>>,
|
state_key_type: Option<Box<Type>>,
|
||||||
unsigned_type: Option<Box<Type>>,
|
unsigned_type: Option<Box<Type>>,
|
||||||
aliases: Vec<LitStr>,
|
aliases: Vec<LitStr>,
|
||||||
@ -101,6 +105,10 @@ impl ContentMeta {
|
|||||||
event_type: either_spanned(self.event_type, other.event_type)?,
|
event_type: either_spanned(self.event_type, other.event_type)?,
|
||||||
event_kind: either_named("event_kind", self.event_kind, other.event_kind)?,
|
event_kind: either_named("event_kind", self.event_kind, other.event_kind)?,
|
||||||
custom_redacted: either_spanned(self.custom_redacted, other.custom_redacted)?,
|
custom_redacted: either_spanned(self.custom_redacted, other.custom_redacted)?,
|
||||||
|
custom_possibly_redacted: either_spanned(
|
||||||
|
self.custom_possibly_redacted,
|
||||||
|
other.custom_possibly_redacted,
|
||||||
|
)?,
|
||||||
state_key_type: either_spanned(self.state_key_type, other.state_key_type)?,
|
state_key_type: either_spanned(self.state_key_type, other.state_key_type)?,
|
||||||
unsigned_type: either_spanned(self.unsigned_type, other.unsigned_type)?,
|
unsigned_type: either_spanned(self.unsigned_type, other.unsigned_type)?,
|
||||||
aliases: [self.aliases, other.aliases].concat(),
|
aliases: [self.aliases, other.aliases].concat(),
|
||||||
@ -128,6 +136,13 @@ impl Parse for ContentMeta {
|
|||||||
let custom_redacted: kw::custom_redacted = input.parse()?;
|
let custom_redacted: kw::custom_redacted = input.parse()?;
|
||||||
|
|
||||||
Ok(Self { custom_redacted: Some(custom_redacted), ..Default::default() })
|
Ok(Self { custom_redacted: Some(custom_redacted), ..Default::default() })
|
||||||
|
} else if lookahead.peek(kw::custom_possibly_redacted) {
|
||||||
|
let custom_possibly_redacted: kw::custom_possibly_redacted = input.parse()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
custom_possibly_redacted: Some(custom_possibly_redacted),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
} else if lookahead.peek(kw::state_key_type) {
|
} else if lookahead.peek(kw::state_key_type) {
|
||||||
let _: kw::state_key_type = input.parse()?;
|
let _: kw::state_key_type = input.parse()?;
|
||||||
let _: Token![=] = input.parse()?;
|
let _: Token![=] = input.parse()?;
|
||||||
@ -162,7 +177,8 @@ struct ContentAttrs {
|
|||||||
state_key_type: Option<TokenStream>,
|
state_key_type: Option<TokenStream>,
|
||||||
unsigned_type: Option<TokenStream>,
|
unsigned_type: Option<TokenStream>,
|
||||||
aliases: Vec<LitStr>,
|
aliases: Vec<LitStr>,
|
||||||
is_custom: bool,
|
is_custom_redacted: bool,
|
||||||
|
is_custom_possibly_redacted: bool,
|
||||||
has_without_relation: bool,
|
has_without_relation: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,6 +190,7 @@ impl TryFrom<ContentMeta> for ContentAttrs {
|
|||||||
event_type,
|
event_type,
|
||||||
event_kind,
|
event_kind,
|
||||||
custom_redacted,
|
custom_redacted,
|
||||||
|
custom_possibly_redacted,
|
||||||
state_key_type,
|
state_key_type,
|
||||||
unsigned_type,
|
unsigned_type,
|
||||||
aliases,
|
aliases,
|
||||||
@ -206,7 +223,8 @@ impl TryFrom<ContentMeta> for ContentAttrs {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_custom = custom_redacted.is_some();
|
let is_custom_redacted = custom_redacted.is_some();
|
||||||
|
let is_custom_possibly_redacted = custom_possibly_redacted.is_some();
|
||||||
|
|
||||||
let unsigned_type = unsigned_type.map(|ty| quote! { #ty });
|
let unsigned_type = unsigned_type.map(|ty| quote! { #ty });
|
||||||
|
|
||||||
@ -244,7 +262,8 @@ impl TryFrom<ContentMeta> for ContentAttrs {
|
|||||||
state_key_type,
|
state_key_type,
|
||||||
unsigned_type,
|
unsigned_type,
|
||||||
aliases,
|
aliases,
|
||||||
is_custom,
|
is_custom_redacted,
|
||||||
|
is_custom_possibly_redacted,
|
||||||
has_without_relation,
|
has_without_relation,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -272,7 +291,8 @@ pub fn expand_event_content(
|
|||||||
state_key_type,
|
state_key_type,
|
||||||
unsigned_type,
|
unsigned_type,
|
||||||
aliases,
|
aliases,
|
||||||
is_custom,
|
is_custom_redacted,
|
||||||
|
is_custom_possibly_redacted,
|
||||||
has_without_relation,
|
has_without_relation,
|
||||||
} = content_meta.try_into()?;
|
} = content_meta.try_into()?;
|
||||||
|
|
||||||
@ -288,7 +308,7 @@ pub fn expand_event_content(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// We only generate redacted content structs for state and message-like events
|
// We only generate redacted content structs for state and message-like events
|
||||||
let redacted_event_content = needs_redacted(is_custom, event_kind).then(|| {
|
let redacted_event_content = needs_redacted(is_custom_redacted, event_kind).then(|| {
|
||||||
generate_redacted_event_content(
|
generate_redacted_event_content(
|
||||||
ident,
|
ident,
|
||||||
fields.clone(),
|
fields.clone(),
|
||||||
@ -302,6 +322,22 @@ pub fn expand_event_content(
|
|||||||
.unwrap_or_else(syn::Error::into_compile_error)
|
.unwrap_or_else(syn::Error::into_compile_error)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// We only generate possibly redacted content structs for state events.
|
||||||
|
let possibly_redacted_event_content =
|
||||||
|
needs_possibly_redacted(is_custom_possibly_redacted, event_kind).then(|| {
|
||||||
|
generate_possibly_redacted_event_content(
|
||||||
|
ident,
|
||||||
|
fields.clone(),
|
||||||
|
&event_type,
|
||||||
|
event_kind,
|
||||||
|
state_key_type.as_ref(),
|
||||||
|
unsigned_type.clone(),
|
||||||
|
&aliases,
|
||||||
|
ruma_common,
|
||||||
|
)
|
||||||
|
.unwrap_or_else(syn::Error::into_compile_error)
|
||||||
|
});
|
||||||
|
|
||||||
let event_content_without_relation = has_without_relation.then(|| {
|
let event_content_without_relation = has_without_relation.then(|| {
|
||||||
generate_event_content_without_relation(ident, fields.clone(), ruma_common)
|
generate_event_content_without_relation(ident, fields.clone(), ruma_common)
|
||||||
.unwrap_or_else(syn::Error::into_compile_error)
|
.unwrap_or_else(syn::Error::into_compile_error)
|
||||||
@ -328,6 +364,7 @@ pub fn expand_event_content(
|
|||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#redacted_event_content
|
#redacted_event_content
|
||||||
|
#possibly_redacted_event_content
|
||||||
#event_content_without_relation
|
#event_content_without_relation
|
||||||
#event_content_impl
|
#event_content_impl
|
||||||
#static_event_content_impl
|
#static_event_content_impl
|
||||||
@ -452,6 +489,157 @@ fn generate_redacted_event_content<'a>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_possibly_redacted_event_content<'a>(
|
||||||
|
ident: &Ident,
|
||||||
|
fields: impl Iterator<Item = &'a Field>,
|
||||||
|
event_type: &LitStr,
|
||||||
|
event_kind: Option<EventKind>,
|
||||||
|
state_key_type: Option<&TokenStream>,
|
||||||
|
unsigned_type: Option<TokenStream>,
|
||||||
|
aliases: &[LitStr],
|
||||||
|
ruma_common: &TokenStream,
|
||||||
|
) -> syn::Result<TokenStream> {
|
||||||
|
assert!(
|
||||||
|
!event_type.value().contains('*'),
|
||||||
|
"Event type shouldn't contain a `*`, this should have been checked previously"
|
||||||
|
);
|
||||||
|
|
||||||
|
let serde = quote! { #ruma_common::exports::serde };
|
||||||
|
|
||||||
|
let doc = format!(
|
||||||
|
"The possibly redacted form of [`{ident}`].\n\n\
|
||||||
|
This type is used when it's not obvious whether the content is redacted or not."
|
||||||
|
);
|
||||||
|
let possibly_redacted_ident = format_ident!("PossiblyRedacted{ident}");
|
||||||
|
|
||||||
|
let mut field_changed = false;
|
||||||
|
let possibly_redacted_fields: Vec<_> = fields
|
||||||
|
.map(|f| {
|
||||||
|
let mut keep_field = false;
|
||||||
|
let mut unsupported_serde_attribute = None;
|
||||||
|
|
||||||
|
if let Type::Path(type_path) = &f.ty {
|
||||||
|
if type_path.path.segments.first().filter(|s| s.ident == "Option").is_some() {
|
||||||
|
// Keep the field if it's an `Option`.
|
||||||
|
keep_field = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut attrs = f
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.map(|a| -> syn::Result<_> {
|
||||||
|
if a.path.is_ident("ruma_event") {
|
||||||
|
// Keep the field if it is not redacted.
|
||||||
|
if let EventFieldMeta::SkipRedaction = a.parse_args()? {
|
||||||
|
keep_field = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't re-emit our `ruma_event` attributes.
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
if a.path.is_ident("serde") {
|
||||||
|
let serde_meta = a.parse_meta()?;
|
||||||
|
|
||||||
|
if let Meta::List(list) = serde_meta {
|
||||||
|
for meta in list.nested.iter().filter_map(|nested_meta| match nested_meta {
|
||||||
|
NestedMeta::Meta(meta) => Some(meta),
|
||||||
|
NestedMeta::Lit(_) => None,
|
||||||
|
}) {
|
||||||
|
if meta.path().is_ident("default") {
|
||||||
|
// Keep the field if it deserializes to its default value.
|
||||||
|
keep_field = true;
|
||||||
|
} else if !meta.path().is_ident("rename") && !meta.path().is_ident("alias") && unsupported_serde_attribute.is_none() {
|
||||||
|
// Error if the field is not kept and uses an unsupported serde attribute.
|
||||||
|
unsupported_serde_attribute = Some(
|
||||||
|
syn::Error::new_spanned(
|
||||||
|
meta,
|
||||||
|
"Can't generate PossiblyRedacted struct with unsupported serde attribute\n\
|
||||||
|
Expected one of `default`, `rename` or `alias`\n\
|
||||||
|
Use the `custom_possibly_redacted` attribute and create the struct manually"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(a.clone()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter_map(Result::transpose)
|
||||||
|
.collect::<syn::Result<_>>()?;
|
||||||
|
|
||||||
|
if keep_field {
|
||||||
|
Ok(Field { attrs, ..f.clone() })
|
||||||
|
} else if let Some(err) = unsupported_serde_attribute {
|
||||||
|
Err(err)
|
||||||
|
} else if f.ident.is_none() {
|
||||||
|
// If the field has no `ident`, it's a tuple struct. Since `content` is an object,
|
||||||
|
// it will need a custom struct to deserialize from an empty object.
|
||||||
|
Err(syn::Error::new(
|
||||||
|
Span::call_site(),
|
||||||
|
"Can't generate PossiblyRedacted struct for tuple structs\n\
|
||||||
|
Use the `custom_possibly_redacted` attribute and create the struct manually",
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
// Change the field to an `Option`.
|
||||||
|
field_changed = true;
|
||||||
|
|
||||||
|
let old_type = &f.ty;
|
||||||
|
let ty = parse_quote!{ Option<#old_type> };
|
||||||
|
attrs.push(parse_quote! { #[serde(skip_serializing_if = "Option::is_none")] });
|
||||||
|
|
||||||
|
Ok(Field { attrs, ty, ..f.clone() })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<syn::Result<_>>()?;
|
||||||
|
|
||||||
|
// If at least one field needs to change, generate a new struct, else use a type alias.
|
||||||
|
if field_changed {
|
||||||
|
let possibly_redacted_event_content = generate_event_content_impl(
|
||||||
|
&possibly_redacted_ident,
|
||||||
|
possibly_redacted_fields.iter(),
|
||||||
|
event_type,
|
||||||
|
event_kind,
|
||||||
|
state_key_type,
|
||||||
|
unsigned_type,
|
||||||
|
aliases,
|
||||||
|
ruma_common,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap_or_else(syn::Error::into_compile_error);
|
||||||
|
|
||||||
|
let static_event_content_impl = event_kind.map(|kind| {
|
||||||
|
generate_static_event_content_impl(
|
||||||
|
&possibly_redacted_ident,
|
||||||
|
kind,
|
||||||
|
true,
|
||||||
|
event_type,
|
||||||
|
ruma_common,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
#[doc = #doc]
|
||||||
|
#[derive(Clone, Debug, #serde::Deserialize, #serde::Serialize)]
|
||||||
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
|
pub struct #possibly_redacted_ident {
|
||||||
|
#( #possibly_redacted_fields, )*
|
||||||
|
}
|
||||||
|
|
||||||
|
#possibly_redacted_event_content
|
||||||
|
|
||||||
|
#static_event_content_impl
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(quote! {
|
||||||
|
#[doc = #doc]
|
||||||
|
pub type #possibly_redacted_ident = #ident;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn generate_event_content_without_relation<'a>(
|
fn generate_event_content_without_relation<'a>(
|
||||||
ident: &Ident,
|
ident: &Ident,
|
||||||
fields: impl Iterator<Item = &'a Field>,
|
fields: impl Iterator<Item = &'a Field>,
|
||||||
@ -679,14 +867,17 @@ fn generate_event_content_impl<'a>(
|
|||||||
let original_state_event_content_impl =
|
let original_state_event_content_impl =
|
||||||
(event_kind == Some(EventKind::State) && is_original).then(|| {
|
(event_kind == Some(EventKind::State) && is_original).then(|| {
|
||||||
let trait_name = format_ident!("Original{kind}Content");
|
let trait_name = format_ident!("Original{kind}Content");
|
||||||
|
let possibly_redacted_ident = format_ident!("PossiblyRedacted{ident}");
|
||||||
|
|
||||||
let unsigned_type = unsigned_type
|
let unsigned_type = unsigned_type.unwrap_or_else(
|
||||||
.unwrap_or_else(|| quote! { #ruma_common::events::StateUnsigned<Self> });
|
|| quote! { #ruma_common::events::StateUnsigned<Self::PossiblyRedacted> },
|
||||||
|
);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl #ruma_common::events::#trait_name for #ident {
|
impl #ruma_common::events::#trait_name for #ident {
|
||||||
type Unsigned = #unsigned_type;
|
type Unsigned = #unsigned_type;
|
||||||
|
type PossiblyRedacted = #possibly_redacted_ident;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -797,9 +988,20 @@ fn generate_static_event_content_impl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn needs_redacted(is_custom: bool, event_kind: Option<EventKind>) -> bool {
|
fn needs_redacted(is_custom_redacted: bool, event_kind: Option<EventKind>) -> bool {
|
||||||
// `is_custom` means that the content struct does not need a generated
|
// `is_custom` means that the content struct does not need a generated
|
||||||
// redacted struct also. If no `custom_redacted` attrs are found the content
|
// redacted struct also. If no `custom_redacted` attrs are found the content
|
||||||
// needs a redacted struct generated.
|
// needs a redacted struct generated.
|
||||||
!is_custom && matches!(event_kind, Some(EventKind::MessageLike) | Some(EventKind::State))
|
!is_custom_redacted
|
||||||
|
&& matches!(event_kind, Some(EventKind::MessageLike) | Some(EventKind::State))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn needs_possibly_redacted(
|
||||||
|
is_custom_possibly_redacted: bool,
|
||||||
|
event_kind: Option<EventKind>,
|
||||||
|
) -> bool {
|
||||||
|
// `is_custom_possibly_redacted` means that the content struct does not need
|
||||||
|
// a generated possibly redacted struct also. If no `custom_possibly_redacted`
|
||||||
|
// attrs are found the content needs a possibly redacted struct generated.
|
||||||
|
!is_custom_possibly_redacted && event_kind == Some(EventKind::State)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user