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
|
||||
* Fix deserialization of `RoomMessageEventContent` and `RoomEncryptedEventContent` when there
|
||||
is no relation
|
||||
* Fix deserialization of `StateUnsigned` when the `prev_content` is redacted
|
||||
|
||||
Breaking changes:
|
||||
|
||||
|
@ -72,6 +72,7 @@ impl StateEventContent for CustomStateEventContent {
|
||||
}
|
||||
impl OriginalStateEventContent for CustomStateEventContent {
|
||||
type Unsigned = StateUnsigned<Self>;
|
||||
type PossiblyRedacted = Self;
|
||||
}
|
||||
impl RedactedStateEventContent for CustomStateEventContent {}
|
||||
|
||||
|
@ -130,6 +130,9 @@ pub trait StateEventContent: EventContent<EventType = StateEventType> {
|
||||
pub trait OriginalStateEventContent: StateEventContent + RedactContent {
|
||||
/// The type of the event's `unsigned` field.
|
||||
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.
|
||||
|
@ -471,7 +471,7 @@ pub struct DecryptedMegolmV1Event<C: MessageLikeEventContent> {
|
||||
/// A non-redacted content also contains the `prev_content` from the unsigned event data.
|
||||
#[allow(clippy::exhaustive_enums)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum FullStateEventContent<C: StateEventContent + RedactContent>
|
||||
pub enum FullStateEventContent<C: OriginalStateEventContent>
|
||||
where
|
||||
C::Redacted: RedactedStateEventContent,
|
||||
{
|
||||
@ -481,14 +481,14 @@ where
|
||||
content: C,
|
||||
|
||||
/// Previous content of the room state.
|
||||
prev_content: Option<C>,
|
||||
prev_content: Option<C::PossiblyRedacted>,
|
||||
},
|
||||
|
||||
/// Redacted content of the event.
|
||||
Redacted(C::Redacted),
|
||||
}
|
||||
|
||||
impl<C: StateEventContent + RedactContent> FullStateEventContent<C>
|
||||
impl<C: OriginalStateEventContent> FullStateEventContent<C>
|
||||
where
|
||||
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.
|
||||
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, StringEnum)]
|
||||
|
@ -4,17 +4,48 @@
|
||||
|
||||
use ruma_macros::EventContent;
|
||||
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.
|
||||
///
|
||||
/// This event type is used to apply rules to room entities.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
||||
#[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);
|
||||
|
||||
/// 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)]
|
||||
mod tests {
|
||||
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 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.
|
||||
///
|
||||
/// This event type is used to apply rules to server entities.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
||||
#[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);
|
||||
|
||||
/// 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 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.
|
||||
///
|
||||
/// This event type is used to apply rules to user entities.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
||||
#[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);
|
||||
|
||||
/// 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,
|
||||
unsigned_type = RoomMemberUnsigned,
|
||||
custom_redacted,
|
||||
custom_possibly_redacted,
|
||||
)]
|
||||
pub struct RoomMemberEventContent {
|
||||
/// 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.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
@ -509,7 +515,7 @@ pub struct RoomMemberUnsigned {
|
||||
pub transaction_id: Option<OwnedTransactionId>,
|
||||
|
||||
/// 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.
|
||||
#[serde(default)]
|
||||
|
@ -60,7 +60,7 @@ fn deserialize_aliases_with_prev_content() {
|
||||
assert_eq!(ev.sender, "@carl:example.com");
|
||||
|
||||
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]
|
||||
@ -78,7 +78,7 @@ fn deserialize_aliases_sync_with_room_id() {
|
||||
assert_eq!(ev.sender, "@carl:example.com");
|
||||
|
||||
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]
|
||||
@ -177,7 +177,7 @@ fn deserialize_full_event_convert_to_sync() {
|
||||
assert_eq!(sync_ev.event_id, "$h29iv0s8:example.com");
|
||||
assert_eq!(sync_ev.origin_server_ts, MilliSecondsSinceUnixEpoch(uint!(1)));
|
||||
assert_eq!(
|
||||
sync_ev.unsigned.prev_content.unwrap().aliases,
|
||||
sync_ev.unsigned.prev_content.unwrap().aliases.unwrap(),
|
||||
vec![room_alias_id!("#inner:localhost")]
|
||||
);
|
||||
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)
|
||||
|
||||
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
|
||||
|
|
||||
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 syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
parse_quote,
|
||||
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;
|
||||
@ -20,6 +21,8 @@ mod kw {
|
||||
syn::custom_keyword!(skip_redaction);
|
||||
// Do not emit any redacted event code.
|
||||
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.
|
||||
syn::custom_keyword!(kind);
|
||||
syn::custom_keyword!(type_fragment);
|
||||
@ -66,6 +69,7 @@ struct ContentMeta {
|
||||
event_type: Option<LitStr>,
|
||||
event_kind: Option<EventKind>,
|
||||
custom_redacted: Option<kw::custom_redacted>,
|
||||
custom_possibly_redacted: Option<kw::custom_possibly_redacted>,
|
||||
state_key_type: Option<Box<Type>>,
|
||||
unsigned_type: Option<Box<Type>>,
|
||||
aliases: Vec<LitStr>,
|
||||
@ -101,6 +105,10 @@ impl ContentMeta {
|
||||
event_type: either_spanned(self.event_type, other.event_type)?,
|
||||
event_kind: either_named("event_kind", self.event_kind, other.event_kind)?,
|
||||
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)?,
|
||||
unsigned_type: either_spanned(self.unsigned_type, other.unsigned_type)?,
|
||||
aliases: [self.aliases, other.aliases].concat(),
|
||||
@ -128,6 +136,13 @@ impl Parse for ContentMeta {
|
||||
let custom_redacted: kw::custom_redacted = input.parse()?;
|
||||
|
||||
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) {
|
||||
let _: kw::state_key_type = input.parse()?;
|
||||
let _: Token![=] = input.parse()?;
|
||||
@ -162,7 +177,8 @@ struct ContentAttrs {
|
||||
state_key_type: Option<TokenStream>,
|
||||
unsigned_type: Option<TokenStream>,
|
||||
aliases: Vec<LitStr>,
|
||||
is_custom: bool,
|
||||
is_custom_redacted: bool,
|
||||
is_custom_possibly_redacted: bool,
|
||||
has_without_relation: bool,
|
||||
}
|
||||
|
||||
@ -174,6 +190,7 @@ impl TryFrom<ContentMeta> for ContentAttrs {
|
||||
event_type,
|
||||
event_kind,
|
||||
custom_redacted,
|
||||
custom_possibly_redacted,
|
||||
state_key_type,
|
||||
unsigned_type,
|
||||
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 });
|
||||
|
||||
@ -244,7 +262,8 @@ impl TryFrom<ContentMeta> for ContentAttrs {
|
||||
state_key_type,
|
||||
unsigned_type,
|
||||
aliases,
|
||||
is_custom,
|
||||
is_custom_redacted,
|
||||
is_custom_possibly_redacted,
|
||||
has_without_relation,
|
||||
})
|
||||
}
|
||||
@ -272,7 +291,8 @@ pub fn expand_event_content(
|
||||
state_key_type,
|
||||
unsigned_type,
|
||||
aliases,
|
||||
is_custom,
|
||||
is_custom_redacted,
|
||||
is_custom_possibly_redacted,
|
||||
has_without_relation,
|
||||
} = 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
|
||||
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(
|
||||
ident,
|
||||
fields.clone(),
|
||||
@ -302,6 +322,22 @@ pub fn expand_event_content(
|
||||
.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(|| {
|
||||
generate_event_content_without_relation(ident, fields.clone(), ruma_common)
|
||||
.unwrap_or_else(syn::Error::into_compile_error)
|
||||
@ -328,6 +364,7 @@ pub fn expand_event_content(
|
||||
|
||||
Ok(quote! {
|
||||
#redacted_event_content
|
||||
#possibly_redacted_event_content
|
||||
#event_content_without_relation
|
||||
#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>(
|
||||
ident: &Ident,
|
||||
fields: impl Iterator<Item = &'a Field>,
|
||||
@ -679,14 +867,17 @@ fn generate_event_content_impl<'a>(
|
||||
let original_state_event_content_impl =
|
||||
(event_kind == Some(EventKind::State) && is_original).then(|| {
|
||||
let trait_name = format_ident!("Original{kind}Content");
|
||||
let possibly_redacted_ident = format_ident!("PossiblyRedacted{ident}");
|
||||
|
||||
let unsigned_type = unsigned_type
|
||||
.unwrap_or_else(|| quote! { #ruma_common::events::StateUnsigned<Self> });
|
||||
let unsigned_type = unsigned_type.unwrap_or_else(
|
||||
|| quote! { #ruma_common::events::StateUnsigned<Self::PossiblyRedacted> },
|
||||
);
|
||||
|
||||
quote! {
|
||||
#[automatically_derived]
|
||||
impl #ruma_common::events::#trait_name for #ident {
|
||||
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
|
||||
// redacted struct also. If no `custom_redacted` attrs are found the content
|
||||
// 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