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,
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,
},
};

View File

@ -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<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)]
mod tests {
use js_int::{int, Int};

View File

@ -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

View File

@ -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);
}

View File

@ -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<IntNotificationPowerLevels> for NotificationPowerLevels {
}
}
#[inline]
pub(crate) fn deserialize_power_levels(
content: &str,
room_version: &RoomVersion,
) -> Option<RoomPowerLevelsEventContent> {
if room_version.integer_power_levels {
match from_json_str::<IntRoomPowerLevelsEventContent>(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<RoomPowerLevelsEventContent> {
match from_json_str::<IntRoomPowerLevelsEventContent>(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<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)]
pub(crate) struct PowerLevelsContentFields {
#[serde(default, deserialize_with = "btreemap_deserialize_v1_powerlevel_values")]
pub(crate) users: BTreeMap<OwnedUserId, Int>,
#[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<OwnedUserId, Int>,
#[serde(default, deserialize_with = "vec_deserialize_int_powerlevel_values")]
users: Vec<(OwnedUserId, Int)>,
#[serde(default)]
users_default: Int,
@ -148,17 +172,30 @@ impl From<IntPowerLevelsContentFields> for PowerLevelsContentFields {
}
}
#[inline]
pub(crate) fn deserialize_power_levels_content_fields(
content: &str,
room_version: &RoomVersion,
) -> Result<PowerLevelsContentFields, Error> {
if room_version.integer_power_levels {
from_json_str::<IntPowerLevelsContentFields>(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<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)]
pub(crate) struct PowerLevelsContentInvite {
#[serde(default, deserialize_with = "deserialize_v1_powerlevel")]