optimize PL deserialization
add generalized map_as_vec deserializer Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
parent
771db61ee4
commit
c1f5f3f20b
@ -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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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};
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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")]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user