From c1f5f3f20b8f7cd14a069d71440cc26607c61827 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Fri, 8 Nov 2024 04:58:01 +0000 Subject: [PATCH] optimize PL deserialization add generalized map_as_vec deserializer Signed-off-by: Jason Volk --- crates/ruma-common/src/serde.rs | 5 +- crates/ruma-common/src/serde/strings.rs | 162 ++++++++++++++++++++++ crates/ruma-state-res/src/event_auth.rs | 4 +- crates/ruma-state-res/src/lib.rs | 2 +- crates/ruma-state-res/src/power_levels.rs | 83 ++++++++--- 5 files changed, 228 insertions(+), 28 deletions(-) diff --git a/crates/ruma-common/src/serde.rs b/crates/ruma-common/src/serde.rs index 99a27db6..7c652efa 100644 --- a/crates/ruma-common/src/serde.rs +++ b/crates/ruma-common/src/serde.rs @@ -27,8 +27,9 @@ pub use self::{ raw::Raw, strings::{ btreemap_deserialize_v1_powerlevel_values, deserialize_as_number_or_string, - deserialize_as_optional_number_or_string, deserialize_v1_powerlevel, empty_string_as_none, - none_as_empty_string, + deserialize_as_optional_number_or_string, deserialize_map_as_vec, + deserialize_v1_powerlevel, empty_string_as_none, none_as_empty_string, + vec_deserialize_int_powerlevel_values, vec_deserialize_v1_powerlevel_values, }, }; diff --git a/crates/ruma-common/src/serde/strings.rs b/crates/ruma-common/src/serde/strings.rs index 7a8d769e..a9ede9c0 100644 --- a/crates/ruma-common/src/serde/strings.rs +++ b/crates/ruma-common/src/serde/strings.rs @@ -252,6 +252,168 @@ where de.deserialize_map(IntMapVisitor::new()) } +/// Take a Map with values of either an integer number or a string and deserialize +/// those to integer numbers in a Vec of sorted pairs. +/// +/// To be used like this: +/// `#[serde(deserialize_with = "vec_deserialize_v1_powerlevel_values")]` +pub fn vec_deserialize_v1_powerlevel_values<'de, D, T>(de: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de> + Ord, +{ + #[repr(transparent)] + struct IntWrap(Int); + + impl<'de> Deserialize<'de> for IntWrap { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_v1_powerlevel(deserializer).map(IntWrap) + } + } + + struct IntMapVisitor { + _phantom: PhantomData, + } + + impl IntMapVisitor { + fn new() -> Self { + Self { _phantom: PhantomData } + } + } + + impl<'de, T> Visitor<'de> for IntMapVisitor + where + T: Deserialize<'de> + Ord, + { + type Value = Vec<(T, Int)>; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a map with integers or strings as values") + } + + fn visit_map>(self, mut map: A) -> Result { + let mut res = Vec::new(); + if let Some(hint) = map.size_hint() { + res.reserve(hint); + } + + while let Some((k, IntWrap(v))) = map.next_entry()? { + res.push((k, v)); + } + + res.sort_unstable(); + res.dedup_by(|a, b| a.0 == b.0); + + Ok(res) + } + } + + de.deserialize_map(IntMapVisitor::new()) +} + +/// Take a Map with integer values and deserialize those to a Vec of sorted pairs +/// +/// To be used like this: +/// `#[serde(deserialize_with = "vec_deserialize_int_powerlevel_values")]` +pub fn vec_deserialize_int_powerlevel_values<'de, D, T>(de: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de> + Ord, +{ + struct IntMapVisitor { + _phantom: PhantomData, + } + + impl IntMapVisitor { + fn new() -> Self { + Self { _phantom: PhantomData } + } + } + + impl<'de, T> Visitor<'de> for IntMapVisitor + where + T: Deserialize<'de> + Ord, + { + type Value = Vec<(T, Int)>; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a map with integers as values") + } + + fn visit_map>(self, mut map: A) -> Result { + let mut res = Vec::new(); + if let Some(hint) = map.size_hint() { + res.reserve(hint); + } + + while let Some(item) = map.next_entry()? { + res.push(item); + } + + res.sort_unstable(); + res.dedup_by(|a, b| a.0 == b.0); + + Ok(res) + } + } + + de.deserialize_map(IntMapVisitor::new()) +} + +/// Deserialize a Map (i.e. JSON Object) to a Vec of sorted pairs. +/// +/// To be used like this: +/// `#[serde(deserialize_with = "deserialize_map_as_vec")]` +pub fn deserialize_map_as_vec<'de, D, K, V>(de: D) -> Result, D::Error> +where + D: Deserializer<'de>, + K: Deserialize<'de> + Ord, + V: Deserialize<'de>, +{ + struct IntMapVisitor { + _phantom: PhantomData, + } + + impl IntMapVisitor { + fn new() -> Self { + Self { _phantom: PhantomData } + } + } + + impl<'de, K, V> Visitor<'de> for IntMapVisitor<(K, V)> + where + K: Deserialize<'de> + Ord, + V: Deserialize<'de>, + { + type Value = Vec<(K, V)>; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a map of keys to values") + } + + fn visit_map>(self, mut map: A) -> Result { + let mut res = Self::Value::new(); + if let Some(hint) = map.size_hint() { + res.reserve(hint); + } + + while let Some(item) = map.next_entry()? { + res.push(item); + } + + res.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + res.dedup_by(|a, b| a.0 == b.0); + + Ok(res) + } + } + + de.deserialize_map(IntMapVisitor::new()) +} + #[cfg(test)] mod tests { use js_int::{int, Int}; diff --git a/crates/ruma-state-res/src/event_auth.rs b/crates/ruma-state-res/src/event_auth.rs index ea79bc62..60d45709 100644 --- a/crates/ruma-state-res/src/event_auth.rs +++ b/crates/ruma-state-res/src/event_auth.rs @@ -354,7 +354,7 @@ where // If type is m.room.third_party_invite let sender_power_level = if let Some(pl) = &power_levels_event { let content = deserialize_power_levels_content_fields(pl.content().get(), room_version)?; - if let Some(level) = content.users.get(sender) { + if let Some(level) = content.get_user_power(sender) { *level } else { content.users_default @@ -528,7 +528,7 @@ fn valid_membership_change( let content = deserialize_power_levels_content_fields(pl.content().get(), room_version)?; - let user_pl = if let Some(level) = content.users.get(user_for_join_auth) { + let user_pl = if let Some(level) = content.get_user_power(user_for_join_auth) { *level } else { content.users_default diff --git a/crates/ruma-state-res/src/lib.rs b/crates/ruma-state-res/src/lib.rs index 642dbdde..99a8dac3 100644 --- a/crates/ruma-state-res/src/lib.rs +++ b/crates/ruma-state-res/src/lib.rs @@ -431,7 +431,7 @@ where }; if let Some(ev) = event { - if let Some(&user_level) = content.users.get(ev.sender()) { + if let Some(&user_level) = content.get_user_power(ev.sender()) { debug!("found {} at power_level {user_level}", ev.sender()); return Ok(user_level); } diff --git a/crates/ruma-state-res/src/power_levels.rs b/crates/ruma-state-res/src/power_levels.rs index ad7a8f17..93a8db04 100644 --- a/crates/ruma-state-res/src/power_levels.rs +++ b/crates/ruma-state-res/src/power_levels.rs @@ -3,8 +3,11 @@ use std::collections::BTreeMap; use js_int::Int; use ruma_common::{ power_levels::{default_power_level, NotificationPowerLevels}, - serde::{btreemap_deserialize_v1_powerlevel_values, deserialize_v1_powerlevel}, - OwnedUserId, + serde::{ + deserialize_v1_powerlevel, vec_deserialize_int_powerlevel_values, + vec_deserialize_v1_powerlevel_values, + }, + OwnedUserId, UserId, }; use ruma_events::{room::power_levels::RoomPowerLevelsEventContent, TimelineEventType}; use serde::Deserialize; @@ -98,44 +101,65 @@ impl From for NotificationPowerLevels { } } +#[inline] pub(crate) fn deserialize_power_levels( content: &str, room_version: &RoomVersion, ) -> Option { if room_version.integer_power_levels { - match from_json_str::(content) { - Ok(content) => Some(content.into()), - Err(_) => { - error!("m.room.power_levels event is not valid with integer values"); - None - } - } + deserialize_integer_power_levels(content) } else { - match from_json_str(content) { - Ok(content) => Some(content), - Err(_) => { - error!( - "m.room.power_levels event is not valid with integer or string integer values" - ); - None - } + deserialize_legacy_power_levels(content) + } +} + +fn deserialize_integer_power_levels(content: &str) -> Option { + match from_json_str::(content) { + Ok(content) => Some(content.into()), + Err(_) => { + error!("m.room.power_levels event is not valid with integer values"); + None + } + } +} + +fn deserialize_legacy_power_levels(content: &str) -> Option { + match from_json_str(content) { + Ok(content) => Some(content), + Err(_) => { + error!("m.room.power_levels event is not valid with integer or string integer values"); + None } } } #[derive(Deserialize)] pub(crate) struct PowerLevelsContentFields { - #[serde(default, deserialize_with = "btreemap_deserialize_v1_powerlevel_values")] - pub(crate) users: BTreeMap, + #[serde(default, deserialize_with = "vec_deserialize_v1_powerlevel_values")] + pub(crate) users: Vec<(OwnedUserId, Int)>, #[serde(default, deserialize_with = "deserialize_v1_powerlevel")] pub(crate) users_default: Int, } +impl PowerLevelsContentFields { + pub(crate) fn get_user_power(&self, user_id: &UserId) -> Option<&Int> { + let comparator = |item: &(OwnedUserId, Int)| { + let item: &UserId = &item.0; + item.cmp(user_id) + }; + + self.users + .binary_search_by(comparator) + .ok() + .and_then(|idx| self.users.get(idx).map(|item| &item.1)) + } +} + #[derive(Deserialize)] struct IntPowerLevelsContentFields { - #[serde(default)] - users: BTreeMap, + #[serde(default, deserialize_with = "vec_deserialize_int_powerlevel_values")] + users: Vec<(OwnedUserId, Int)>, #[serde(default)] users_default: Int, @@ -148,17 +172,30 @@ impl From for PowerLevelsContentFields { } } +#[inline] pub(crate) fn deserialize_power_levels_content_fields( content: &str, room_version: &RoomVersion, ) -> Result { if room_version.integer_power_levels { - from_json_str::(content).map(|r| r.into()) + deserialize_integer_power_levels_content_fields(content) } else { - from_json_str(content) + deserialize_legacy_power_levels_content_fields(content) } } +fn deserialize_integer_power_levels_content_fields( + content: &str, +) -> Result { + from_json_str::(content).map(|r| r.into()) +} + +fn deserialize_legacy_power_levels_content_fields( + content: &str, +) -> Result { + from_json_str(content) +} + #[derive(Deserialize)] pub(crate) struct PowerLevelsContentInvite { #[serde(default, deserialize_with = "deserialize_v1_powerlevel")]