1#![allow(clippy::too_many_arguments)] use std::{borrow::Cow, fmt};
5
6use proc_macro2::{Span, TokenStream};
7use quote::{format_ident, quote, ToTokens};
8use syn::{
9 parse::{Parse, ParseStream},
10 parse_quote,
11 punctuated::Punctuated,
12 DeriveInput, Field, Ident, LitStr, Meta, Token, Type,
13};
14
15use super::event_parse::{EventKind, EventKindVariation};
16use crate::util::{m_prefix_name_to_type_name, PrivateField};
17
18mod kw {
19 syn::custom_keyword!(skip_redaction);
21 syn::custom_keyword!(custom_redacted);
23 syn::custom_keyword!(custom_possibly_redacted);
25 syn::custom_keyword!(kind);
27 syn::custom_keyword!(type_fragment);
28 syn::custom_keyword!(state_key_type);
30 syn::custom_keyword!(unsigned_type);
32 syn::custom_keyword!(alias);
34 syn::custom_keyword!(without_relation);
36}
37
38enum EventFieldMeta {
42 SkipRedaction,
45
46 TypeFragment,
49}
50
51impl Parse for EventFieldMeta {
52 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
53 let lookahead = input.lookahead1();
54 if lookahead.peek(kw::skip_redaction) {
55 let _: kw::skip_redaction = input.parse()?;
56 Ok(EventFieldMeta::SkipRedaction)
57 } else if lookahead.peek(kw::type_fragment) {
58 let _: kw::type_fragment = input.parse()?;
59 Ok(EventFieldMeta::TypeFragment)
60 } else {
61 Err(lookahead.error())
62 }
63 }
64}
65
66#[derive(Default)]
67struct ContentMeta {
68 event_type: Option<LitStr>,
69 event_kind: Option<EventKind>,
70 custom_redacted: Option<kw::custom_redacted>,
71 custom_possibly_redacted: Option<kw::custom_possibly_redacted>,
72 state_key_type: Option<Box<Type>>,
73 unsigned_type: Option<Box<Type>>,
74 aliases: Vec<LitStr>,
75 without_relation: Option<kw::without_relation>,
76}
77
78impl ContentMeta {
79 fn merge(self, other: ContentMeta) -> syn::Result<Self> {
80 fn either_spanned<T: ToTokens>(a: Option<T>, b: Option<T>) -> syn::Result<Option<T>> {
81 match (a, b) {
82 (None, None) => Ok(None),
83 (Some(val), None) | (None, Some(val)) => Ok(Some(val)),
84 (Some(a), Some(b)) => {
85 let mut error = syn::Error::new_spanned(a, "redundant attribute argument");
86 error.combine(syn::Error::new_spanned(b, "note: first one here"));
87 Err(error)
88 }
89 }
90 }
91
92 fn either_named<T>(name: &str, a: Option<T>, b: Option<T>) -> syn::Result<Option<T>> {
93 match (a, b) {
94 (None, None) => Ok(None),
95 (Some(val), None) | (None, Some(val)) => Ok(Some(val)),
96 (Some(_), Some(_)) => Err(syn::Error::new(
97 Span::call_site(),
98 format!("multiple {name} attributes found, there can only be one"),
99 )),
100 }
101 }
102
103 Ok(Self {
104 event_type: either_spanned(self.event_type, other.event_type)?,
105 event_kind: either_named("event_kind", self.event_kind, other.event_kind)?,
106 custom_redacted: either_spanned(self.custom_redacted, other.custom_redacted)?,
107 custom_possibly_redacted: either_spanned(
108 self.custom_possibly_redacted,
109 other.custom_possibly_redacted,
110 )?,
111 state_key_type: either_spanned(self.state_key_type, other.state_key_type)?,
112 unsigned_type: either_spanned(self.unsigned_type, other.unsigned_type)?,
113 aliases: [self.aliases, other.aliases].concat(),
114 without_relation: either_spanned(self.without_relation, other.without_relation)?,
115 })
116 }
117}
118
119impl Parse for ContentMeta {
120 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
121 let lookahead = input.lookahead1();
122 if lookahead.peek(Token![type]) {
123 let _: Token![type] = input.parse()?;
124 let _: Token![=] = input.parse()?;
125 let event_type = input.parse()?;
126
127 Ok(Self { event_type: Some(event_type), ..Default::default() })
128 } else if lookahead.peek(kw::kind) {
129 let _: kw::kind = input.parse()?;
130 let _: Token![=] = input.parse()?;
131 let event_kind = input.parse()?;
132
133 Ok(Self { event_kind: Some(event_kind), ..Default::default() })
134 } else if lookahead.peek(kw::custom_redacted) {
135 let custom_redacted: kw::custom_redacted = input.parse()?;
136
137 Ok(Self { custom_redacted: Some(custom_redacted), ..Default::default() })
138 } else if lookahead.peek(kw::custom_possibly_redacted) {
139 let custom_possibly_redacted: kw::custom_possibly_redacted = input.parse()?;
140
141 Ok(Self {
142 custom_possibly_redacted: Some(custom_possibly_redacted),
143 ..Default::default()
144 })
145 } else if lookahead.peek(kw::state_key_type) {
146 let _: kw::state_key_type = input.parse()?;
147 let _: Token![=] = input.parse()?;
148 let state_key_type = input.parse()?;
149
150 Ok(Self { state_key_type: Some(state_key_type), ..Default::default() })
151 } else if lookahead.peek(kw::unsigned_type) {
152 let _: kw::unsigned_type = input.parse()?;
153 let _: Token![=] = input.parse()?;
154 let unsigned_type = input.parse()?;
155
156 Ok(Self { unsigned_type: Some(unsigned_type), ..Default::default() })
157 } else if lookahead.peek(kw::alias) {
158 let _: kw::alias = input.parse()?;
159 let _: Token![=] = input.parse()?;
160 let alias = input.parse()?;
161
162 Ok(Self { aliases: vec![alias], ..Default::default() })
163 } else if lookahead.peek(kw::without_relation) {
164 let without_relation: kw::without_relation = input.parse()?;
165
166 Ok(Self { without_relation: Some(without_relation), ..Default::default() })
167 } else {
168 Err(lookahead.error())
169 }
170 }
171}
172
173struct ContentAttrs {
174 event_type: LitStr,
175 event_kind: Option<EventKind>,
176 state_key_type: Option<TokenStream>,
177 unsigned_type: Option<TokenStream>,
178 aliases: Vec<LitStr>,
179 is_custom_redacted: bool,
180 is_custom_possibly_redacted: bool,
181 has_without_relation: bool,
182}
183
184impl TryFrom<ContentMeta> for ContentAttrs {
185 type Error = syn::Error;
186
187 fn try_from(value: ContentMeta) -> Result<Self, Self::Error> {
188 let ContentMeta {
189 event_type,
190 event_kind,
191 custom_redacted,
192 custom_possibly_redacted,
193 state_key_type,
194 unsigned_type,
195 aliases,
196 without_relation,
197 } = value;
198
199 let event_type = event_type.ok_or_else(|| {
200 syn::Error::new(
201 Span::call_site(),
202 "no event type attribute found, \
203 add `#[ruma_event(type = \"any.room.event\", kind = Kind)]` \
204 below the event content derive",
205 )
206 })?;
207
208 let state_key_type = match (event_kind, state_key_type) {
209 (Some(EventKind::State), None) => {
210 return Err(syn::Error::new(
211 Span::call_site(),
212 "no state_key_type attribute found, please specify one",
213 ));
214 }
215 (Some(EventKind::State), Some(ty)) => Some(quote! { #ty }),
216 (_, None) => None,
217 (_, Some(ty)) => {
218 return Err(syn::Error::new_spanned(
219 ty,
220 "state_key_type attribute is not valid for non-state event kinds",
221 ));
222 }
223 };
224
225 let is_custom_redacted = custom_redacted.is_some();
226 let is_custom_possibly_redacted = custom_possibly_redacted.is_some();
227
228 let unsigned_type = unsigned_type.map(|ty| quote! { #ty });
229
230 let event_type_s = event_type.value();
231 let prefix = event_type_s.strip_suffix(".*");
232
233 if prefix.unwrap_or(&event_type_s).contains('*') {
234 return Err(syn::Error::new_spanned(
235 event_type,
236 "event type may only contain `*` as part of a `.*` suffix",
237 ));
238 }
239
240 if prefix.is_some() && !event_kind.is_some_and(|k| k.is_account_data()) {
241 return Err(syn::Error::new_spanned(
242 event_type,
243 "only account data events may contain a `.*` suffix",
244 ));
245 }
246
247 for alias in &aliases {
248 if alias.value().ends_with(".*") != prefix.is_some() {
249 return Err(syn::Error::new_spanned(
250 alias,
251 "aliases should have the same `.*` suffix, or lack thereof, as the main event type",
252 ));
253 }
254 }
255
256 let has_without_relation = without_relation.is_some();
257
258 Ok(Self {
259 event_type,
260 event_kind,
261 state_key_type,
262 unsigned_type,
263 aliases,
264 is_custom_redacted,
265 is_custom_possibly_redacted,
266 has_without_relation,
267 })
268 }
269}
270
271pub fn expand_event_content(
273 input: &DeriveInput,
274 ruma_events: &TokenStream,
275) -> syn::Result<TokenStream> {
276 let content_meta = input
277 .attrs
278 .iter()
279 .filter(|attr| attr.path().is_ident("ruma_event"))
280 .try_fold(ContentMeta::default(), |meta, attr| {
281 let list: Punctuated<ContentMeta, Token![,]> =
282 attr.parse_args_with(Punctuated::parse_terminated)?;
283
284 list.into_iter().try_fold(meta, ContentMeta::merge)
285 })?;
286
287 let ContentAttrs {
288 event_type,
289 event_kind,
290 state_key_type,
291 unsigned_type,
292 aliases,
293 is_custom_redacted,
294 is_custom_possibly_redacted,
295 has_without_relation,
296 } = content_meta.try_into()?;
297
298 let ident = &input.ident;
299 let fields = match &input.data {
300 syn::Data::Struct(syn::DataStruct { fields, .. }) => Some(fields.iter()),
301 _ => {
302 if event_kind.is_some_and(|kind| needs_redacted(is_custom_redacted, kind)) {
303 return Err(syn::Error::new(
304 Span::call_site(),
305 "To generate a redacted event content, the event content type needs to be a struct. Disable this with the custom_redacted attribute",
306 ));
307 }
308
309 if event_kind.is_some_and(|kind| needs_possibly_redacted(is_custom_redacted, kind)) {
310 return Err(syn::Error::new(
311 Span::call_site(),
312 "To generate a possibly redacted event content, the event content type needs to be a struct. Disable this with the custom_possibly_redacted attribute",
313 ));
314 }
315
316 if has_without_relation {
317 return Err(syn::Error::new(
318 Span::call_site(),
319 "To generate an event content without relation, the event content type needs to be a struct. Disable this by removing the without_relation attribute",
320 ));
321 }
322
323 None
324 }
325 };
326
327 let redacted_event_content =
329 event_kind.filter(|kind| needs_redacted(is_custom_redacted, *kind)).map(|kind| {
330 generate_redacted_event_content(
331 ident,
332 &input.vis,
333 fields.clone().unwrap(),
334 &event_type,
335 kind,
336 state_key_type.as_ref(),
337 unsigned_type.clone(),
338 &aliases,
339 ruma_events,
340 )
341 .unwrap_or_else(syn::Error::into_compile_error)
342 });
343
344 let possibly_redacted_event_content = event_kind
346 .filter(|kind| needs_possibly_redacted(is_custom_possibly_redacted, *kind))
347 .map(|_| {
348 generate_possibly_redacted_event_content(
349 ident,
350 &input.vis,
351 fields.clone().unwrap(),
352 &event_type,
353 state_key_type.as_ref(),
354 unsigned_type.clone(),
355 &aliases,
356 ruma_events,
357 )
358 .unwrap_or_else(syn::Error::into_compile_error)
359 });
360
361 let event_content_without_relation = has_without_relation.then(|| {
362 generate_event_content_without_relation(
363 ident,
364 &input.vis,
365 fields.clone().unwrap(),
366 ruma_events,
367 )
368 .unwrap_or_else(syn::Error::into_compile_error)
369 });
370
371 let event_content_impl = generate_event_content_impl(
372 ident,
373 &input.vis,
374 fields,
375 &event_type,
376 event_kind,
377 EventKindContentVariation::Original,
378 state_key_type.as_ref(),
379 unsigned_type,
380 &aliases,
381 ruma_events,
382 )
383 .unwrap_or_else(syn::Error::into_compile_error);
384 let static_event_content_impl =
385 generate_static_event_content_impl(ident, &event_type, ruma_events);
386 let type_aliases = event_kind.map(|k| {
387 generate_event_type_aliases(k, ident, &input.vis, &event_type.value(), ruma_events)
388 .unwrap_or_else(syn::Error::into_compile_error)
389 });
390
391 Ok(quote! {
392 #redacted_event_content
393 #possibly_redacted_event_content
394 #event_content_without_relation
395 #event_content_impl
396 #static_event_content_impl
397 #type_aliases
398 })
399}
400
401fn generate_redacted_event_content<'a>(
402 ident: &Ident,
403 vis: &syn::Visibility,
404 fields: impl Iterator<Item = &'a Field>,
405 event_type: &LitStr,
406 event_kind: EventKind,
407 state_key_type: Option<&TokenStream>,
408 unsigned_type: Option<TokenStream>,
409 aliases: &[LitStr],
410 ruma_events: &TokenStream,
411) -> syn::Result<TokenStream> {
412 assert!(
413 !event_type.value().contains('*'),
414 "Event type shouldn't contain a `*`, this should have been checked previously"
415 );
416
417 let ruma_common = quote! { #ruma_events::exports::ruma_common };
418 let serde = quote! { #ruma_events::exports::serde };
419
420 let doc = format!("Redacted form of [`{ident}`]");
421 let redacted_ident = format_ident!("Redacted{ident}");
422
423 let kept_redacted_fields: Vec<_> = fields
424 .map(|f| {
425 let mut keep_field = false;
426 let attrs = f
427 .attrs
428 .iter()
429 .map(|a| -> syn::Result<_> {
430 if a.path().is_ident("ruma_event") {
431 if let EventFieldMeta::SkipRedaction = a.parse_args()? {
432 keep_field = true;
433 }
434
435 Ok(None)
437 } else {
438 Ok(Some(a.clone()))
439 }
440 })
441 .filter_map(Result::transpose)
442 .collect::<syn::Result<_>>()?;
443
444 if keep_field {
445 Ok(Some(Field { attrs, ..f.clone() }))
446 } else {
447 Ok(None)
448 }
449 })
450 .filter_map(Result::transpose)
451 .collect::<syn::Result<_>>()?;
452
453 let redaction_struct_fields = kept_redacted_fields.iter().flat_map(|f| &f.ident);
454
455 let constructor = kept_redacted_fields.is_empty().then(|| {
456 let doc = format!("Creates an empty {redacted_ident}.");
457 quote! {
458 impl #redacted_ident {
459 #[doc = #doc]
460 #vis fn new() -> Self {
461 Self {}
462 }
463 }
464 }
465 });
466
467 let redacted_event_content = generate_event_content_impl(
468 &redacted_ident,
469 vis,
470 Some(kept_redacted_fields.iter()),
471 event_type,
472 Some(event_kind),
473 EventKindContentVariation::Redacted,
474 state_key_type,
475 unsigned_type,
476 aliases,
477 ruma_events,
478 )
479 .unwrap_or_else(syn::Error::into_compile_error);
480
481 let static_event_content_impl =
482 generate_static_event_content_impl(&redacted_ident, event_type, ruma_events);
483
484 Ok(quote! {
485 #[automatically_derived]
487 impl #ruma_events::RedactContent for #ident {
488 type Redacted = #redacted_ident;
489
490 fn redact(self, _rules: &#ruma_common::room_version_rules::RedactionRules) -> #redacted_ident {
491 #redacted_ident {
492 #( #redaction_struct_fields: self.#redaction_struct_fields, )*
493 }
494 }
495 }
496
497 #[doc = #doc]
498 #[derive(Clone, Debug, #serde::Deserialize, #serde::Serialize)]
499 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
500 #vis struct #redacted_ident {
501 #( #kept_redacted_fields, )*
502 }
503
504 #constructor
505
506 #redacted_event_content
507
508 #static_event_content_impl
509 })
510}
511
512fn generate_possibly_redacted_event_content<'a>(
513 ident: &Ident,
514 vis: &syn::Visibility,
515 fields: impl Iterator<Item = &'a Field>,
516 event_type: &LitStr,
517 state_key_type: Option<&TokenStream>,
518 unsigned_type: Option<TokenStream>,
519 aliases: &[LitStr],
520 ruma_events: &TokenStream,
521) -> syn::Result<TokenStream> {
522 assert!(
523 !event_type.value().contains('*'),
524 "Event type shouldn't contain a `*`, this should have been checked previously"
525 );
526
527 let serde = quote! { #ruma_events::exports::serde };
528
529 let doc = format!(
530 "The possibly redacted form of [`{ident}`].\n\n\
531 This type is used when it's not obvious whether the content is redacted or not."
532 );
533 let possibly_redacted_ident = format_ident!("PossiblyRedacted{ident}");
534
535 let mut field_changed = false;
536 let possibly_redacted_fields: Vec<_> = fields
537 .map(|f| {
538 let mut keep_field = false;
539 let mut unsupported_serde_attribute = None;
540
541 if let Type::Path(type_path) = &f.ty {
542 if type_path.path.segments.first().filter(|s| s.ident == "Option").is_some() {
543 keep_field = true;
545 }
546 }
547
548 let mut attrs = f
549 .attrs
550 .iter()
551 .map(|a| -> syn::Result<_> {
552 if a.path().is_ident("ruma_event") {
553 if let EventFieldMeta::SkipRedaction = a.parse_args()? {
555 keep_field = true;
556 }
557
558 Ok(None)
560 } else {
561 if a.path().is_ident("serde") {
562 if let Meta::List(list) = &a.meta {
563 let nested: Punctuated<Meta, Token![,]> =
564 list.parse_args_with(Punctuated::parse_terminated)?;
565 for meta in &nested {
566 if meta.path().is_ident("default") {
567 keep_field = true;
569 } else if !meta.path().is_ident("rename")
570 && !meta.path().is_ident("alias")
571 && unsupported_serde_attribute.is_none()
572 {
573 unsupported_serde_attribute =
576 Some(syn::Error::new_spanned(
577 meta,
578 "Can't generate PossiblyRedacted struct with \
579 unsupported serde attribute\n\
580 Expected one of `default`, `rename` or `alias`\n\
581 Use the `custom_possibly_redacted` attribute \
582 and create the struct manually",
583 ));
584 }
585 }
586 }
587 }
588
589 Ok(Some(a.clone()))
590 }
591 })
592 .filter_map(Result::transpose)
593 .collect::<syn::Result<_>>()?;
594
595 if keep_field {
596 Ok(Field { attrs, ..f.clone() })
597 } else if let Some(err) = unsupported_serde_attribute {
598 Err(err)
599 } else if f.ident.is_none() {
600 Err(syn::Error::new(
603 Span::call_site(),
604 "Can't generate PossiblyRedacted struct for tuple structs\n\
605 Use the `custom_possibly_redacted` attribute and create the struct manually",
606 ))
607 } else {
608 field_changed = true;
610
611 let old_type = &f.ty;
612 let ty = parse_quote! { Option<#old_type> };
613 attrs.push(parse_quote! { #[serde(skip_serializing_if = "Option::is_none")] });
614
615 Ok(Field { attrs, ty, ..f.clone() })
616 }
617 })
618 .collect::<syn::Result<_>>()?;
619
620 if field_changed {
622 let possibly_redacted_event_content = generate_event_content_impl(
623 &possibly_redacted_ident,
624 vis,
625 Some(possibly_redacted_fields.iter()),
626 event_type,
627 Some(EventKind::State),
628 EventKindContentVariation::PossiblyRedacted,
629 state_key_type,
630 unsigned_type,
631 aliases,
632 ruma_events,
633 )
634 .unwrap_or_else(syn::Error::into_compile_error);
635
636 let static_event_content_impl =
637 generate_static_event_content_impl(&possibly_redacted_ident, event_type, ruma_events);
638
639 Ok(quote! {
640 #[doc = #doc]
641 #[derive(Clone, Debug, #serde::Deserialize, #serde::Serialize)]
642 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
643 #vis struct #possibly_redacted_ident {
644 #( #possibly_redacted_fields, )*
645 }
646
647 #possibly_redacted_event_content
648
649 #static_event_content_impl
650 })
651 } else {
652 Ok(quote! {
653 #[doc = #doc]
654 #vis type #possibly_redacted_ident = #ident;
655
656 #[automatically_derived]
657 impl #ruma_events::PossiblyRedactedStateEventContent for #ident {
658 type StateKey = #state_key_type;
659 }
660 })
661 }
662}
663
664fn generate_event_content_without_relation<'a>(
665 ident: &Ident,
666 vis: &syn::Visibility,
667 fields: impl Iterator<Item = &'a Field>,
668 ruma_events: &TokenStream,
669) -> syn::Result<TokenStream> {
670 let serde = quote! { #ruma_events::exports::serde };
671
672 let type_doc = format!(
673 "Form of [`{ident}`] without relation.\n\n\
674 To construct this type, construct a [`{ident}`] and then use one of its `::from()` / `.into()` methods."
675 );
676 let without_relation_ident = format_ident!("{ident}WithoutRelation");
677
678 let with_relation_fn_doc =
679 format!("Transform `self` into a [`{ident}`] with the given relation.");
680
681 let (relates_to, other_fields) = fields.partition::<Vec<_>, _>(|f| {
682 f.ident.as_ref().filter(|ident| *ident == "relates_to").is_some()
683 });
684
685 let relates_to_type = relates_to.into_iter().next().map(|f| &f.ty).ok_or_else(|| {
686 syn::Error::new(
687 Span::call_site(),
688 "`without_relation` can only be used on events with a `relates_to` field",
689 )
690 })?;
691
692 let without_relation_fields = other_fields.iter().flat_map(|f| &f.ident).collect::<Vec<_>>();
693 let without_relation_struct = if other_fields.is_empty() {
694 quote! { ; }
695 } else {
696 quote! {
697 { #( #other_fields, )* }
698 }
699 };
700
701 Ok(quote! {
702 #[allow(unused_qualifications)]
703 #[automatically_derived]
704 impl ::std::convert::From<#ident> for #without_relation_ident {
705 fn from(c: #ident) -> Self {
706 Self {
707 #( #without_relation_fields: c.#without_relation_fields, )*
708 }
709 }
710 }
711
712 #[doc = #type_doc]
713 #[derive(Clone, Debug, #serde::Deserialize, #serde::Serialize)]
714 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
715 #vis struct #without_relation_ident #without_relation_struct
716
717 impl #without_relation_ident {
718 #[doc = #with_relation_fn_doc]
719 #vis fn with_relation(self, relates_to: #relates_to_type) -> #ident {
720 #ident {
721 #( #without_relation_fields: self.#without_relation_fields, )*
722 relates_to,
723 }
724 }
725 }
726 })
727}
728
729fn generate_event_type_aliases(
730 event_kind: EventKind,
731 ident: &Ident,
732 vis: &syn::Visibility,
733 event_type: &str,
734 ruma_events: &TokenStream,
735) -> syn::Result<TokenStream> {
736 if ident == "RoomRedactionEventContent" {
738 return Ok(quote! {});
739 }
740
741 let ident_s = ident.to_string();
742 let ev_type_s = ident_s.strip_suffix("Content").ok_or_else(|| {
743 syn::Error::new_spanned(ident, "Expected content struct name ending in `Content`")
744 })?;
745
746 let type_aliases = [
747 EventKindVariation::None,
748 EventKindVariation::Sync,
749 EventKindVariation::Original,
750 EventKindVariation::OriginalSync,
751 EventKindVariation::Stripped,
752 EventKindVariation::Initial,
753 EventKindVariation::Redacted,
754 EventKindVariation::RedactedSync,
755 ]
756 .iter()
757 .filter_map(|&var| Some((var, event_kind.to_event_ident(var).ok()?)))
758 .map(|(var, ev_struct)| {
759 let ev_type = format_ident!("{var}{ev_type_s}");
760
761 let doc_text = match var {
762 EventKindVariation::None | EventKindVariation::Original => "",
763 EventKindVariation::Sync | EventKindVariation::OriginalSync => {
764 " from a `sync_events` response"
765 }
766 EventKindVariation::Stripped => " from an invited room preview",
767 EventKindVariation::Redacted => " that has been redacted",
768 EventKindVariation::RedactedSync => {
769 " from a `sync_events` response that has been redacted"
770 }
771 EventKindVariation::Initial => " for creating a room",
772 };
773 let ev_type_doc = format!("An `{event_type}` event{doc_text}.");
774
775 let content_struct = if var.is_redacted() {
776 Cow::Owned(format_ident!("Redacted{ident}"))
777 } else if let EventKindVariation::Stripped = var {
778 Cow::Owned(format_ident!("PossiblyRedacted{ident}"))
779 } else {
780 Cow::Borrowed(ident)
781 };
782
783 quote! {
784 #[doc = #ev_type_doc]
785 #vis type #ev_type = #ruma_events::#ev_struct<#content_struct>;
786 }
787 })
788 .collect();
789
790 Ok(type_aliases)
791}
792
793#[derive(PartialEq)]
794enum EventKindContentVariation {
795 Original,
796 Redacted,
797 PossiblyRedacted,
798}
799
800impl fmt::Display for EventKindContentVariation {
801 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
802 match self {
803 EventKindContentVariation::Original => Ok(()),
804 EventKindContentVariation::Redacted => write!(f, "Redacted"),
805 EventKindContentVariation::PossiblyRedacted => write!(f, "PossiblyRedacted"),
806 }
807 }
808}
809
810fn generate_event_content_impl<'a>(
811 ident: &Ident,
812 vis: &syn::Visibility,
813 mut fields: Option<impl Iterator<Item = &'a Field>>,
814 event_type: &LitStr,
815 event_kind: Option<EventKind>,
816 variation: EventKindContentVariation,
817 state_key_type: Option<&TokenStream>,
818 unsigned_type: Option<TokenStream>,
819 aliases: &[LitStr],
820 ruma_events: &TokenStream,
821) -> syn::Result<TokenStream> {
822 let serde = quote! { #ruma_events::exports::serde };
823 let serde_json = quote! { #ruma_events::exports::serde_json };
824
825 let (event_type_ty_decl, event_type_ty, event_type_fn_impl);
826
827 let type_suffix_data = event_type
828 .value()
829 .strip_suffix('*')
830 .map(|type_prefix| {
831 let Some(fields) = &mut fields else {
832 return Err(syn::Error::new_spanned(
833 event_type,
834 "event type with a `.*` suffix is required to be a struct",
835 ));
836 };
837
838 let type_fragment_field = fields
839 .find_map(|f| {
840 f.attrs.iter().filter(|a| a.path().is_ident("ruma_event")).find_map(|attr| {
841 match attr.parse_args() {
842 Ok(EventFieldMeta::TypeFragment) => Some(Ok(f)),
843 Ok(_) => None,
844 Err(e) => Some(Err(e)),
845 }
846 })
847 })
848 .transpose()?
849 .ok_or_else(|| {
850 syn::Error::new_spanned(
851 event_type,
852 "event type with a `.*` suffix requires there to be a \
853 `#[ruma_event(type_fragment)]` field",
854 )
855 })?
856 .ident
857 .as_ref()
858 .expect("type fragment field needs to have a name");
859
860 <syn::Result<_>>::Ok((type_prefix.to_owned(), type_fragment_field))
861 })
862 .transpose()?;
863
864 match event_kind {
865 Some(kind) => {
866 let i = kind.to_event_type_enum();
867 event_type_ty_decl = None;
868 event_type_ty = quote! { #ruma_events::#i };
869 event_type_fn_impl = match &type_suffix_data {
870 Some((type_prefix, type_fragment_field)) => {
871 let format = type_prefix.to_owned() + "{}";
872
873 quote! {
874 ::std::convert::From::from(::std::format!(#format, self.#type_fragment_field))
875 }
876 }
877 None => quote! { ::std::convert::From::from(#event_type) },
878 };
879 }
880 None => {
881 let camel_case_type_name = m_prefix_name_to_type_name(event_type)?;
882 let i = format_ident!("{}EventType", camel_case_type_name);
883 event_type_ty_decl = Some(quote! {
884 #[doc(hidden)]
886 #vis struct #i {
887 ty: ::std::option::Option<crate::PrivOwnedStr>,
889 }
890
891 impl #serde::Serialize for #i {
892 fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
893 where
894 S: #serde::Serializer,
895 {
896 let s = self.ty.as_ref().map(|t| &t.0[..]).unwrap_or(#event_type);
897 serializer.serialize_str(s)
898 }
899 }
900 });
901 event_type_ty = quote! { #i };
902 event_type_fn_impl = quote! { #event_type_ty { ty: ::std::option::Option::None } };
903 }
904 }
905
906 let sub_trait_impl = event_kind.map(|kind| {
907 let trait_name = format_ident!("{variation}{kind}Content");
908
909 let state_key = (kind == EventKind::State).then(|| {
910 assert!(state_key_type.is_some());
911
912 quote! {
913 type StateKey = #state_key_type;
914 }
915 });
916
917 quote! {
918 #[automatically_derived]
919 impl #ruma_events::#trait_name for #ident {
920 #state_key
921 }
922 }
923 });
924
925 let static_state_event_content_impl = (event_kind == Some(EventKind::State)
926 && variation == EventKindContentVariation::Original)
927 .then(|| {
928 let possibly_redacted_ident = format_ident!("PossiblyRedacted{ident}");
929
930 let unsigned_type = unsigned_type
931 .unwrap_or_else(|| quote! { #ruma_events::StateUnsigned<Self::PossiblyRedacted> });
932
933 quote! {
934 #[automatically_derived]
935 impl #ruma_events::StaticStateEventContent for #ident {
936 type PossiblyRedacted = #possibly_redacted_ident;
937 type Unsigned = #unsigned_type;
938 }
939 }
940 });
941
942 let event_types = aliases.iter().chain([event_type]);
943
944 let event_content_from_type_impl = type_suffix_data.map(|(_, type_fragment_field)| {
945 let type_prefixes = event_types.map(|ev_type| {
946 ev_type
947 .value()
948 .strip_suffix('*')
949 .expect("aliases have already been checked to have the same suffix")
950 .to_owned()
951 });
952 let type_prefixes = quote! {
953 [#(#type_prefixes,)*]
954 };
955 let fields_without_type_fragment = fields
956 .unwrap()
957 .filter(|f| {
958 !f.attrs.iter().any(|a| {
959 a.path().is_ident("ruma_event")
960 && matches!(a.parse_args(), Ok(EventFieldMeta::TypeFragment))
961 })
962 })
963 .map(PrivateField)
964 .collect::<Vec<_>>();
965 let fields_ident_without_type_fragment =
966 fields_without_type_fragment.iter().filter_map(|f| f.0.ident.as_ref());
967
968 quote! {
969 impl #ruma_events::EventContentFromType for #ident {
970 fn from_parts(
971 ev_type: &::std::primitive::str,
972 content: &#serde_json::value::RawValue,
973 ) -> #serde_json::Result<Self> {
974 #[derive(#serde::Deserialize)]
975 struct WithoutTypeFragment {
976 #( #fields_without_type_fragment, )*
977 }
978
979 if let ::std::option::Option::Some(type_fragment) =
980 #type_prefixes.iter().find_map(|prefix| ev_type.strip_prefix(prefix))
981 {
982 let c: WithoutTypeFragment = #serde_json::from_str(content.get())?;
983
984 ::std::result::Result::Ok(Self {
985 #(
986 #fields_ident_without_type_fragment:
987 c.#fields_ident_without_type_fragment,
988 )*
989 #type_fragment_field: type_fragment.to_owned(),
990 })
991 } else {
992 ::std::result::Result::Err(#serde::de::Error::custom(
993 ::std::format!(
994 "expected event type starting with one of `{:?}`, found `{}`",
995 #type_prefixes, ev_type,
996 )
997 ))
998 }
999 }
1000 }
1001 }
1002 });
1003
1004 Ok(quote! {
1005 #event_type_ty_decl
1006
1007 #[automatically_derived]
1008 impl #ruma_events::EventContent for #ident {
1009 type EventType = #event_type_ty;
1010
1011 fn event_type(&self) -> Self::EventType {
1012 #event_type_fn_impl
1013 }
1014 }
1015
1016 #event_content_from_type_impl
1017 #sub_trait_impl
1018 #static_state_event_content_impl
1019 })
1020}
1021
1022fn generate_static_event_content_impl(
1023 ident: &Ident,
1024 event_type: &LitStr,
1025 ruma_events: &TokenStream,
1026) -> TokenStream {
1027 quote! {
1028 impl #ruma_events::StaticEventContent for #ident {
1029 const TYPE: &'static ::std::primitive::str = #event_type;
1030 }
1031 }
1032}
1033
1034fn needs_redacted(is_custom_redacted: bool, event_kind: EventKind) -> bool {
1035 !is_custom_redacted && matches!(event_kind, EventKind::MessageLike | EventKind::State)
1039}
1040
1041fn needs_possibly_redacted(is_custom_possibly_redacted: bool, event_kind: EventKind) -> bool {
1042 !is_custom_possibly_redacted && event_kind == EventKind::State
1046}