optimize PL deserialization

add generalized map_as_vec deserializer

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk 2024-11-08 04:58:01 +00:00
parent 771db61ee4
commit c1f5f3f20b
5 changed files with 228 additions and 28 deletions

View File

@ -27,8 +27,9 @@ pub use self::{
raw::Raw, raw::Raw,
strings::{ strings::{
btreemap_deserialize_v1_powerlevel_values, deserialize_as_number_or_string, btreemap_deserialize_v1_powerlevel_values, deserialize_as_number_or_string,
deserialize_as_optional_number_or_string, deserialize_v1_powerlevel, empty_string_as_none, deserialize_as_optional_number_or_string, deserialize_map_as_vec,
none_as_empty_string, deserialize_v1_powerlevel, empty_string_as_none, none_as_empty_string,
vec_deserialize_int_powerlevel_values, vec_deserialize_v1_powerlevel_values,
}, },
}; };

View File

@ -252,6 +252,168 @@ where
de.deserialize_map(IntMapVisitor::new()) 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<Vec<(T, Int)>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + Ord,
{
#[repr(transparent)]
struct IntWrap(Int);
impl<'de> Deserialize<'de> for IntWrap {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_v1_powerlevel(deserializer).map(IntWrap)
}
}
struct IntMapVisitor<T> {
_phantom: PhantomData<T>,
}
impl<T> IntMapVisitor<T> {
fn new() -> Self {
Self { _phantom: PhantomData }
}
}
impl<'de, T> Visitor<'de> for IntMapVisitor<T>
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<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
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<Vec<(T, Int)>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + Ord,
{
struct IntMapVisitor<T> {
_phantom: PhantomData<T>,
}
impl<T> IntMapVisitor<T> {
fn new() -> Self {
Self { _phantom: PhantomData }
}
}
impl<'de, T> Visitor<'de> for IntMapVisitor<T>
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<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
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<Vec<(K, V)>, D::Error>
where
D: Deserializer<'de>,
K: Deserialize<'de> + Ord,
V: Deserialize<'de>,
{
struct IntMapVisitor<T> {
_phantom: PhantomData<T>,
}
impl<T> IntMapVisitor<T> {
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<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
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)] #[cfg(test)]
mod tests { mod tests {
use js_int::{int, Int}; use js_int::{int, Int};

View File

@ -354,7 +354,7 @@ where
// If type is m.room.third_party_invite // If type is m.room.third_party_invite
let sender_power_level = if let Some(pl) = &power_levels_event { let sender_power_level = if let Some(pl) = &power_levels_event {
let content = deserialize_power_levels_content_fields(pl.content().get(), room_version)?; 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 *level
} else { } else {
content.users_default content.users_default
@ -528,7 +528,7 @@ fn valid_membership_change(
let content = let content =
deserialize_power_levels_content_fields(pl.content().get(), room_version)?; 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 *level
} else { } else {
content.users_default content.users_default

View File

@ -431,7 +431,7 @@ where
}; };
if let Some(ev) = event { 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()); debug!("found {} at power_level {user_level}", ev.sender());
return Ok(user_level); return Ok(user_level);
} }

View File

@ -3,8 +3,11 @@ use std::collections::BTreeMap;
use js_int::Int; use js_int::Int;
use ruma_common::{ use ruma_common::{
power_levels::{default_power_level, NotificationPowerLevels}, power_levels::{default_power_level, NotificationPowerLevels},
serde::{btreemap_deserialize_v1_powerlevel_values, deserialize_v1_powerlevel}, serde::{
OwnedUserId, deserialize_v1_powerlevel, vec_deserialize_int_powerlevel_values,
vec_deserialize_v1_powerlevel_values,
},
OwnedUserId, UserId,
}; };
use ruma_events::{room::power_levels::RoomPowerLevelsEventContent, TimelineEventType}; use ruma_events::{room::power_levels::RoomPowerLevelsEventContent, TimelineEventType};
use serde::Deserialize; use serde::Deserialize;
@ -98,44 +101,65 @@ impl From<IntNotificationPowerLevels> for NotificationPowerLevels {
} }
} }
#[inline]
pub(crate) fn deserialize_power_levels( pub(crate) fn deserialize_power_levels(
content: &str, content: &str,
room_version: &RoomVersion, room_version: &RoomVersion,
) -> Option<RoomPowerLevelsEventContent> { ) -> Option<RoomPowerLevelsEventContent> {
if room_version.integer_power_levels { if room_version.integer_power_levels {
match from_json_str::<IntRoomPowerLevelsEventContent>(content) { deserialize_integer_power_levels(content)
Ok(content) => Some(content.into()),
Err(_) => {
error!("m.room.power_levels event is not valid with integer values");
None
}
}
} else { } else {
match from_json_str(content) { deserialize_legacy_power_levels(content)
Ok(content) => Some(content), }
Err(_) => { }
error!(
"m.room.power_levels event is not valid with integer or string integer values" fn deserialize_integer_power_levels(content: &str) -> Option<RoomPowerLevelsEventContent> {
); match from_json_str::<IntRoomPowerLevelsEventContent>(content) {
None 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<RoomPowerLevelsEventContent> {
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)] #[derive(Deserialize)]
pub(crate) struct PowerLevelsContentFields { pub(crate) struct PowerLevelsContentFields {
#[serde(default, deserialize_with = "btreemap_deserialize_v1_powerlevel_values")] #[serde(default, deserialize_with = "vec_deserialize_v1_powerlevel_values")]
pub(crate) users: BTreeMap<OwnedUserId, Int>, pub(crate) users: Vec<(OwnedUserId, Int)>,
#[serde(default, deserialize_with = "deserialize_v1_powerlevel")] #[serde(default, deserialize_with = "deserialize_v1_powerlevel")]
pub(crate) users_default: Int, 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)] #[derive(Deserialize)]
struct IntPowerLevelsContentFields { struct IntPowerLevelsContentFields {
#[serde(default)] #[serde(default, deserialize_with = "vec_deserialize_int_powerlevel_values")]
users: BTreeMap<OwnedUserId, Int>, users: Vec<(OwnedUserId, Int)>,
#[serde(default)] #[serde(default)]
users_default: Int, users_default: Int,
@ -148,17 +172,30 @@ impl From<IntPowerLevelsContentFields> for PowerLevelsContentFields {
} }
} }
#[inline]
pub(crate) fn deserialize_power_levels_content_fields( pub(crate) fn deserialize_power_levels_content_fields(
content: &str, content: &str,
room_version: &RoomVersion, room_version: &RoomVersion,
) -> Result<PowerLevelsContentFields, Error> { ) -> Result<PowerLevelsContentFields, Error> {
if room_version.integer_power_levels { if room_version.integer_power_levels {
from_json_str::<IntPowerLevelsContentFields>(content).map(|r| r.into()) deserialize_integer_power_levels_content_fields(content)
} else { } else {
from_json_str(content) deserialize_legacy_power_levels_content_fields(content)
} }
} }
fn deserialize_integer_power_levels_content_fields(
content: &str,
) -> Result<PowerLevelsContentFields, Error> {
from_json_str::<IntPowerLevelsContentFields>(content).map(|r| r.into())
}
fn deserialize_legacy_power_levels_content_fields(
content: &str,
) -> Result<PowerLevelsContentFields, Error> {
from_json_str(content)
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub(crate) struct PowerLevelsContentInvite { pub(crate) struct PowerLevelsContentInvite {
#[serde(default, deserialize_with = "deserialize_v1_powerlevel")] #[serde(default, deserialize_with = "deserialize_v1_powerlevel")]