diff --git a/src/gen.rs b/src/gen.rs index 6971fccf..894d96db 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -71,6 +71,9 @@ impl From for RumaEvent { } impl ToTokens for RumaEvent { + // TODO: Maybe break this off into functions so it's not so large. Then remove the clippy + // allowance. + #[allow(clippy::cognitive_complexity)] fn to_tokens(&self, tokens: &mut TokenStream) { let attrs = &self.attrs; let content_name = &self.content_name; @@ -116,17 +119,84 @@ impl ToTokens for RumaEvent { let field_count = event_fields.len() + 1; // + 1 because of manually adding `event_type` + let mut from_str_field_values: Vec = Vec::with_capacity(event_fields.len()); let mut serialize_field_calls: Vec = Vec::with_capacity(event_fields.len()); for field in event_fields { let ident = field.ident.clone().unwrap(); let ident_str = format!("{}", ident); - let token_stream = quote! { + let from_str_field_value = if ident == "content" { + match &self.content { + Content::Struct(content_fields) => { + let mut content_field_values: Vec = + Vec::with_capacity(content_fields.len()); + + for content_field in content_fields { + let content_field_ident = content_field.ident.clone().unwrap(); + + let token_stream = quote! { + #content_field_ident: raw.content.#content_field_ident, + }; + + content_field_values.push(token_stream); + } + + quote! { + content: #content_name { + #(#content_field_values),* + }, + } + } + Content::Typedef(_) => { + quote! { + content: raw.content, + } + } + } + } else if ident == "prev_content" { + match &self.content { + Content::Struct(content_fields) => { + let mut content_field_values: Vec = + Vec::with_capacity(content_fields.len()); + + for content_field in content_fields { + let content_field_ident = content_field.ident.clone().unwrap(); + + let token_stream = quote! { + #content_field_ident: prev.#content_field_ident, + }; + + content_field_values.push(token_stream); + } + + quote! { + prev_content: raw.prev_content.map(|prev| { + #content_name { + #(#content_field_values),* + } + }), + } + } + Content::Typedef(_) => { + quote! { + content: raw.content, + } + } + } + } else { + quote! { + #ident: raw.#ident, + } + }; + + from_str_field_values.push(from_str_field_value); + + let serialize_field_call = quote! { state.serialize_field(#ident_str, &self.#ident)?; }; - serialize_field_calls.push(token_stream); + serialize_field_calls.push(serialize_field_call); } let output = quote!( @@ -138,6 +208,17 @@ impl ToTokens for RumaEvent { #content + impl #name { + /// Attempt to create `Self` from parsing a string of JSON data. + pub fn from_str(json: &str) -> Result { + let raw = serde_json::from_str::(json)?; + + Ok(Self { + #(#from_str_field_values)* + }) + } + } + use serde::ser::SerializeStruct as _; impl serde::Serialize for #name { diff --git a/tests/ruma_events_macros.rs b/tests/ruma_events_macros.rs index fa077be8..e8257c6d 100644 --- a/tests/ruma_events_macros.rs +++ b/tests/ruma_events_macros.rs @@ -35,6 +35,14 @@ where } } +pub struct InvalidEvent; + +impl From for InvalidEvent { + fn from(_: serde_json::Error) -> Self { + Self + } +} + // See note about wrapping macro expansion in a module from `src/lib.rs` pub mod common_case { use super::Event;