diff --git a/ruma-events-macros/src/event.rs b/ruma-events-macros/src/event.rs index 86986580..8507a970 100644 --- a/ruma-events-macros/src/event.rs +++ b/ruma-events-macros/src/event.rs @@ -2,7 +2,9 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed, Ident}; +use syn::{ + Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed, Ident, Meta, MetaList, NestedMeta, +}; use crate::{ event_parse::{to_kind_variation, EventKind, EventKindVariation}, @@ -42,7 +44,7 @@ pub fn expand_event(input: DeriveInput) -> syn::Result { }; let serialize_impl = expand_serialize_event(&input, &var, &fields, &ruma_events); - let deserialize_impl = expand_deserialize_event(&input, &var, &fields, &ruma_events); + let deserialize_impl = expand_deserialize_event(&input, &var, &fields, &ruma_events)?; let conversion_impl = expand_from_into(&input, &kind, &var, &fields, &ruma_events); let eq_impl = expand_eq_ord_event(&input, &fields); @@ -133,7 +135,7 @@ fn expand_deserialize_event( var: &EventKindVariation, fields: &[Field], ruma_events: &TokenStream, -) -> TokenStream { +) -> syn::Result { let js_int = quote! { #ruma_events::exports::js_int }; let serde = quote! { #ruma_events::exports::serde }; let serde_json = quote! { #ruma_events::exports::serde_json }; @@ -181,7 +183,7 @@ fn expand_deserialize_event( .iter() .map(|field| { let name = field.ident.as_ref().unwrap(); - if name == "content" { + Ok(if name == "content" { if is_generic && var.is_redacted() { quote! { let content = match C::has_deserialize_fields() { @@ -246,14 +248,37 @@ fn expand_deserialize_event( } else if name == "unsigned" { quote! { let unsigned = unsigned.unwrap_or_default(); } } else { - quote! { - let #name = #name.ok_or_else(|| { - #serde::de::Error::missing_field(stringify!(#name)) - })?; + let attrs: Vec<_> = field + .attrs + .iter() + .filter(|a| a.path.is_ident("ruma_event")) + .map(|a| a.parse_meta()) + .collect::>()?; + + let has_default_attr = attrs.iter().any(|a| { + matches!( + a, + Meta::List(MetaList { nested, .. }) + if nested.iter().any(|n| { + matches!(n, NestedMeta::Meta(Meta::Path(p)) if p.is_ident("default")) + }) + ) + }); + + if has_default_attr { + quote! { + let #name = #name.unwrap_or_default(); + } + } else { + quote! { + let #name = #name.ok_or_else(|| { + #serde::de::Error::missing_field(stringify!(#name)) + })?; + } } - } + }) }) - .collect(); + .collect::>()?; let field_names: Vec<_> = fields.iter().flat_map(|f| &f.ident).collect(); @@ -269,7 +294,7 @@ fn expand_deserialize_event( quote! {} }; - quote! { + Ok(quote! { #[automatically_derived] impl #deserialize_impl_gen #serde::de::Deserialize<'de> for #ident #ty_gen #where_clause { fn deserialize(deserializer: D) -> Result @@ -350,7 +375,7 @@ fn expand_deserialize_event( deserializer.deserialize_map(EventVisitor(#deserialize_phantom_type)) } } - } + }) } fn expand_from_into( diff --git a/ruma-events/src/event_kinds.rs b/ruma-events/src/event_kinds.rs index 33e04dca..43c037f5 100644 --- a/ruma-events/src/event_kinds.rs +++ b/ruma-events/src/event_kinds.rs @@ -226,6 +226,9 @@ pub struct InitialStateEvent { /// /// This is often an empty string, but some events send a `UserId` to show /// which user the event affects. + /// + /// Defaults to the empty string. + #[ruma_event(default)] pub state_key: String, } diff --git a/ruma-events/tests/initial_state.rs b/ruma-events/tests/initial_state.rs new file mode 100644 index 00000000..56bc3921 --- /dev/null +++ b/ruma-events/tests/initial_state.rs @@ -0,0 +1,16 @@ +use matches::assert_matches; +use ruma_events::{AnyInitialStateEvent, InitialStateEvent}; +use serde_json::json; + +#[test] +fn deserialize_initial_state_event() { + assert_matches!( + serde_json::from_value(json!({ + "type": "m.room.name", + "content": { "name": "foo" } + })) + .unwrap(), + AnyInitialStateEvent::RoomName(InitialStateEvent { content, state_key}) + if content.name() == Some("foo") && state_key.is_empty() + ); +}