diff --git a/data_model/derive/src/event_set.rs b/data_model/derive/src/event_set.rs index af04bb7f99a..8fb87bad479 100644 --- a/data_model/derive/src/event_set.rs +++ b/data_model/derive/src/event_set.rs @@ -132,16 +132,15 @@ impl ToTokens for EventSetEnum { } = self; // definitions of consts for each event - let flag_defs = variants.iter().enumerate().map( + let flag_defs = variants.iter().zip(0u32..).map( |( - i, EventSetVariant { flag_ident, event_ident, .. }, + i, )| { - let i = u32::try_from(i).unwrap(); let doc = format!(" Matches [`{event_enum_ident}::{event_ident}`]"); quote! { #[doc = #doc] @@ -152,23 +151,24 @@ impl ToTokens for EventSetEnum { // identifiers of all the flag constants to use in impls let flag_idents = variants .iter() - .map( - |EventSetVariant { - flag_ident: ident, .. - }| quote!(Self::#ident), - ) + .map(|EventSetVariant { flag_ident, .. }| quote!(#set_ident::#flag_ident)) .collect::>(); // names of all the flag (as string literals) to use in debug impl - let flag_names = variants.iter().map( - |EventSetVariant { - flag_ident: ident, .. - }| { - let flag_name = ident.to_string(); + let flag_names = variants + .iter() + .map(|EventSetVariant { flag_ident, .. }| { + let flag_name = flag_ident.to_string(); quote! { #flag_name } - }, - ); + }) + .collect::>(); + // names of all the flags in `quotes` to use in error message + let flag_names_str = variants + .iter() + .map(|EventSetVariant { flag_ident, .. }| format!("`{flag_ident}`")) + .collect::>() + .join(", "); // patterns for matching events in the `matches` method let event_patterns = variants.iter().map( |EventSetVariant { @@ -197,8 +197,6 @@ impl ToTokens for EventSetEnum { // but it's the easiest way to make sure those traits are implemented parity_scale_codec::Decode, parity_scale_codec::Encode, - serde::Deserialize, - serde::Serialize, // TODO: we probably want to represent the bit values for each variant in the schema iroha_schema::IntoSchema, )] @@ -241,6 +239,19 @@ impl ToTokens for EventSetEnum { (self.0 & other.0) == other.0 } + /// Decomposes an `EventSet` into a vector of basis `EventSet`s, each containing a single event + /// + /// Each of the event set in the vector will be equal to some of the associated constants for the `EventSet` + pub fn decompose(&self) -> Vec { + let mut result = Vec::new(); + + #(if self.contains(#flag_idents) { + result.push(#flag_idents); + })* + + result + } + /// Checks whether an event set contains a specific event pub const fn matches(&self, event: &#event_enum_ident) -> bool { match event { @@ -277,6 +288,98 @@ impl ToTokens for EventSetEnum { self.or(rhs) } } + + impl core::ops::BitOrAssign for #set_ident { + fn bitor_assign(&mut self, rhs: Self) { + *self = self.or(rhs); + } + } + + impl serde::Serialize for #set_ident { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::*; + + let basis = self.decompose(); + let mut seq = serializer.serialize_seq(Some(basis.len()))?; + for event in basis { + let str = match event { + #(#flag_idents => #flag_names,)* + _ => unreachable!(), + }; + + seq.serialize_element(&str)?; + } + seq.end() + } + } + + impl<'de> serde::Deserialize<'de> for #set_ident { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = #set_ident; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str("a sequence of strings") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut result = #set_ident::empty(); + + struct SingleEvent(#set_ident); + impl<'de> serde::Deserialize<'de> for SingleEvent { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = SingleEvent; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str("a string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + let event = match v { + #( + #flag_names => #flag_idents, + )* + _ => return Err(serde::de::Error::custom(format!("unknown event variant `{}`, expected one of {}", v, #flag_names_str))), + }; + + Ok(SingleEvent(event)) + } + } + + deserializer.deserialize_str(Visitor) + } + } + while let Some(SingleEvent(event)) = seq.next_element::()? { + result |= event; + } + + Ok(result) + } + } + + deserializer.deserialize_seq(Visitor) + } + } }) } } diff --git a/data_model/derive/src/lib.rs b/data_model/derive/src/lib.rs index 399e7efffa3..7e81563c7fb 100644 --- a/data_model/derive/src/lib.rs +++ b/data_model/derive/src/lib.rs @@ -519,6 +519,8 @@ pub fn has_origin_derive(input: TokenStream) -> TokenStream { /// const fn or(self, other: TestEventSet) -> TestEventSet; /// /// Checks if the `other` set is a subset of `self` set /// const fn contains(&self, other: Self) -> bool; +/// /// Decomposes an `EventSet` into a vector of basis `EventSet`s, each containing a single event +/// fn decompose(&self) -> Vec /// /// Checks if the set contains a specific event /// const fn matches(&self, event: &TestEvent) -> bool; /// } @@ -536,8 +538,6 @@ pub fn has_origin_derive(input: TokenStream) -> TokenStream { /// Hash, /// parity_scale_codec::Decode, /// parity_scale_codec::Encode, -/// serde::Deserialize -/// serde::Serialize, /// iroha_schema::IntoSchema, /// )] /// diff --git a/data_model/derive/tests/event_set.rs b/data_model/derive/tests/event_set.rs index 071a3a98712..97169e3b8ad 100644 --- a/data_model/derive/tests/event_set.rs +++ b/data_model/derive/tests/event_set.rs @@ -11,6 +11,89 @@ mod events { } use events::{AnotherEvent, TestEvent, TestEventSet}; +use serde_json::json; + +#[test] +fn serialize() { + assert_eq!( + serde_json::to_value(TestEventSet::Event1).unwrap(), + json!(["Event1"]) + ); + assert_eq!( + serde_json::to_value(TestEventSet::Event1 | TestEventSet::Event2).unwrap(), + json!(["Event1", "Event2"]) + ); + assert_eq!( + serde_json::to_value(TestEventSet::Event1 | TestEventSet::AnyNestedEvent).unwrap(), + json!(["Event1", "AnyNestedEvent"]) + ); + assert_eq!( + serde_json::to_value(TestEventSet::all()).unwrap(), + json!(["Event1", "Event2", "AnyNestedEvent"]) + ); +} + +#[test] +fn deserialize() { + assert_eq!( + serde_json::from_value::(json!([])).unwrap(), + TestEventSet::empty() + ); + assert_eq!( + serde_json::from_value::(json!(["Event1"])).unwrap(), + TestEventSet::Event1 + ); + assert_eq!( + serde_json::from_value::(json!(["Event1", "Event2"])).unwrap(), + TestEventSet::Event1 | TestEventSet::Event2 + ); + assert_eq!( + serde_json::from_value::(json!(["Event1", "AnyNestedEvent"])).unwrap(), + TestEventSet::Event1 | TestEventSet::AnyNestedEvent + ); + assert_eq!( + serde_json::from_value::(json!(["Event1", "Event2", "AnyNestedEvent"])) + .unwrap(), + TestEventSet::all(), + ); + + assert_eq!( + serde_json::from_value::(json!(["Event1", "Event1", "AnyNestedEvent"])) + .unwrap(), + TestEventSet::Event1 | TestEventSet::AnyNestedEvent, + ); +} + +#[test] +fn deserialize_invalid() { + assert_eq!( + serde_json::from_value::(json!(32)) + .unwrap_err() + .to_string(), + "invalid type: integer `32`, expected a sequence of strings" + ); + + assert_eq!( + serde_json::from_value::(json!([32])) + .unwrap_err() + .to_string(), + "invalid type: integer `32`, expected a string" + ); + + assert_eq!( + serde_json::from_value::(json!(["InvalidVariant"])) + .unwrap_err() + .to_string(), + "unknown event variant `InvalidVariant`, expected one of `Event1`, `Event2`, `AnyNestedEvent`" + ); + + assert_eq!( + serde_json::from_value::(json!(["Event1", "Event1", "InvalidVariant"])) + .unwrap_err() + .to_string(), + "unknown event variant `InvalidVariant`, expected one of `Event1`, `Event2`, `AnyNestedEvent`" + ); +} #[test] fn full_set() { @@ -89,3 +172,19 @@ fn event1_or_2_set() { assert!(event1_or_2_matcher.matches(&TestEvent::Event2)); assert!(!event1_or_2_matcher.matches(&TestEvent::NestedEvent(AnotherEvent))); } + +#[test] +fn repeated() { + assert_eq!( + TestEventSet::Event1 | TestEventSet::Event1, + TestEventSet::Event1 + ); + assert_eq!( + TestEventSet::Event1 | TestEventSet::Event2 | TestEventSet::Event1, + TestEventSet::Event1 | TestEventSet::Event2 + ); + assert_eq!( + TestEventSet::all() | TestEventSet::AnyNestedEvent, + TestEventSet::all() + ); +} diff --git a/tools/parity_scale_decoder/samples/trigger.bin b/tools/parity_scale_decoder/samples/trigger.bin index 606a8247e2f..bf1bf3f4b9e 100644 Binary files a/tools/parity_scale_decoder/samples/trigger.bin and b/tools/parity_scale_decoder/samples/trigger.bin differ diff --git a/tools/parity_scale_decoder/samples/trigger.json b/tools/parity_scale_decoder/samples/trigger.json index f487af7b26e..bcc84114c88 100644 --- a/tools/parity_scale_decoder/samples/trigger.json +++ b/tools/parity_scale_decoder/samples/trigger.json @@ -19,7 +19,7 @@ "Data": { "Domain": { "id_matcher": null, - "event_set": "AnyAccount" + "event_set": ["AnyAccount"] } } },