push: Move FlattenedJson to its own module
This commit is contained in:
parent
326c77aad3
commit
cdfcdecaf8
@ -5,18 +5,21 @@ use regex::Regex;
|
|||||||
#[cfg(feature = "unstable-msc3931")]
|
#[cfg(feature = "unstable-msc3931")]
|
||||||
use ruma_macros::StringEnum;
|
use ruma_macros::StringEnum;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{to_value as to_json_value, value::Value as JsonValue};
|
use serde_json::value::Value as JsonValue;
|
||||||
use tracing::{instrument, warn};
|
|
||||||
use wildmatch::WildMatch;
|
use wildmatch::WildMatch;
|
||||||
|
|
||||||
use crate::{power_levels::NotificationPowerLevels, serde::Raw, OwnedRoomId, OwnedUserId, UserId};
|
use crate::{power_levels::NotificationPowerLevels, OwnedRoomId, OwnedUserId, UserId};
|
||||||
#[cfg(feature = "unstable-msc3931")]
|
#[cfg(feature = "unstable-msc3931")]
|
||||||
use crate::{PrivOwnedStr, RoomVersionId};
|
use crate::{PrivOwnedStr, RoomVersionId};
|
||||||
|
|
||||||
|
mod flattened_json;
|
||||||
mod push_condition_serde;
|
mod push_condition_serde;
|
||||||
mod room_member_count_is;
|
mod room_member_count_is;
|
||||||
|
|
||||||
pub use room_member_count_is::{ComparisonOperator, RoomMemberCountIs};
|
pub use self::{
|
||||||
|
flattened_json::FlattenedJson,
|
||||||
|
room_member_count_is::{ComparisonOperator, RoomMemberCountIs},
|
||||||
|
};
|
||||||
|
|
||||||
/// Features supported by room versions.
|
/// Features supported by room versions.
|
||||||
#[cfg(feature = "unstable-msc3931")]
|
#[cfg(feature = "unstable-msc3931")]
|
||||||
@ -410,61 +413,12 @@ impl StrExt for str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The flattened representation of a JSON object.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct FlattenedJson {
|
|
||||||
/// The internal map containing the flattened JSON as a pair path, value.
|
|
||||||
map: BTreeMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FlattenedJson {
|
|
||||||
/// Create a `FlattenedJson` from `Raw`.
|
|
||||||
pub fn from_raw<T>(raw: &Raw<T>) -> Self {
|
|
||||||
let mut s = Self { map: BTreeMap::new() };
|
|
||||||
s.flatten_value(to_json_value(raw).unwrap(), "".into());
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Flatten and insert the `value` at `path`.
|
|
||||||
#[instrument(skip(self, value))]
|
|
||||||
fn flatten_value(&mut self, value: JsonValue, path: String) {
|
|
||||||
match value {
|
|
||||||
JsonValue::Object(fields) => {
|
|
||||||
for (key, value) in fields {
|
|
||||||
let key = escape_key(&key);
|
|
||||||
let path = if path.is_empty() { key } else { format!("{path}.{key}") };
|
|
||||||
self.flatten_value(value, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JsonValue::String(s) => {
|
|
||||||
if self.map.insert(path.clone(), s).is_some() {
|
|
||||||
warn!("Duplicate path in flattened JSON: {path}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JsonValue::Number(_) | JsonValue::Bool(_) | JsonValue::Array(_) | JsonValue::Null => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Value associated with the given `path`.
|
|
||||||
pub fn get(&self, path: &str) -> Option<&str> {
|
|
||||||
self.map.get(path).map(|s| s.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Escape a key for path matching.
|
|
||||||
///
|
|
||||||
/// This escapes the dots (`.`) and backslashes (`\`) in the key with a backslash.
|
|
||||||
fn escape_key(key: &str) -> String {
|
|
||||||
key.replace('\\', r"\\").replace('.', r"\.")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
use js_int::{int, uint};
|
use js_int::{int, uint};
|
||||||
use maplit::btreemap;
|
|
||||||
use serde_json::{
|
use serde_json::{
|
||||||
from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue,
|
from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue,
|
||||||
};
|
};
|
||||||
@ -796,53 +750,4 @@ mod tests {
|
|||||||
assert!(room_version_condition.applies(&simple_event, &context_matching));
|
assert!(room_version_condition.applies(&simple_event, &context_matching));
|
||||||
assert!(!room_version_condition.applies(&simple_event, &context_not_matching));
|
assert!(!room_version_condition.applies(&simple_event, &context_not_matching));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn flattened_json_values() {
|
|
||||||
let raw = serde_json::from_str::<Raw<JsonValue>>(
|
|
||||||
r#"{
|
|
||||||
"string": "Hello World",
|
|
||||||
"number": 10,
|
|
||||||
"array": [1, 2],
|
|
||||||
"boolean": true,
|
|
||||||
"null": null
|
|
||||||
}"#,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let flattened = FlattenedJson::from_raw(&raw);
|
|
||||||
assert_eq!(flattened.map, btreemap! { "string".into() => "Hello World".into() });
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn flattened_json_nested() {
|
|
||||||
let raw = serde_json::from_str::<Raw<JsonValue>>(
|
|
||||||
r#"{
|
|
||||||
"desc": "Level 0",
|
|
||||||
"desc.bis": "Level 0 bis",
|
|
||||||
"up": {
|
|
||||||
"desc": "Level 1",
|
|
||||||
"desc.bis": "Level 1 bis",
|
|
||||||
"up": {
|
|
||||||
"desc": "Level 2",
|
|
||||||
"desc\\bis": "Level 2 bis"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let flattened = FlattenedJson::from_raw(&raw);
|
|
||||||
assert_eq!(
|
|
||||||
flattened.map,
|
|
||||||
btreemap! {
|
|
||||||
"desc".into() => "Level 0".into(),
|
|
||||||
r"desc\.bis".into() => "Level 0 bis".into(),
|
|
||||||
"up.desc".into() => "Level 1".into(),
|
|
||||||
r"up.desc\.bis".into() => "Level 1 bis".into(),
|
|
||||||
"up.up.desc".into() => "Level 2".into(),
|
|
||||||
r"up.up.desc\\bis".into() => "Level 2 bis".into(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
111
crates/ruma-common/src/push/condition/flattened_json.rs
Normal file
111
crates/ruma-common/src/push/condition/flattened_json.rs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
use serde_json::{to_value as to_json_value, value::Value as JsonValue};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use tracing::{instrument, warn};
|
||||||
|
|
||||||
|
use crate::serde::Raw;
|
||||||
|
|
||||||
|
/// The flattened representation of a JSON object.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct FlattenedJson {
|
||||||
|
/// The internal map containing the flattened JSON as a pair path, value.
|
||||||
|
map: BTreeMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlattenedJson {
|
||||||
|
/// Create a `FlattenedJson` from `Raw`.
|
||||||
|
pub fn from_raw<T>(raw: &Raw<T>) -> Self {
|
||||||
|
let mut s = Self { map: BTreeMap::new() };
|
||||||
|
s.flatten_value(to_json_value(raw).unwrap(), "".into());
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flatten and insert the `value` at `path`.
|
||||||
|
#[instrument(skip(self, value))]
|
||||||
|
fn flatten_value(&mut self, value: JsonValue, path: String) {
|
||||||
|
match value {
|
||||||
|
JsonValue::Object(fields) => {
|
||||||
|
for (key, value) in fields {
|
||||||
|
let key = escape_key(&key);
|
||||||
|
let path = if path.is_empty() { key } else { format!("{path}.{key}") };
|
||||||
|
self.flatten_value(value, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JsonValue::String(s) => {
|
||||||
|
if self.map.insert(path.clone(), s).is_some() {
|
||||||
|
warn!("Duplicate path in flattened JSON: {path}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JsonValue::Number(_) | JsonValue::Bool(_) | JsonValue::Array(_) | JsonValue::Null => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Value associated with the given `path`.
|
||||||
|
pub fn get(&self, path: &str) -> Option<&str> {
|
||||||
|
self.map.get(path).map(|s| s.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Escape a key for path matching.
|
||||||
|
///
|
||||||
|
/// This escapes the dots (`.`) and backslashes (`\`) in the key with a backslash.
|
||||||
|
fn escape_key(key: &str) -> String {
|
||||||
|
key.replace('\\', r"\\").replace('.', r"\.")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use maplit::btreemap;
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use super::FlattenedJson;
|
||||||
|
use crate::serde::Raw;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flattened_json_values() {
|
||||||
|
let raw = serde_json::from_str::<Raw<JsonValue>>(
|
||||||
|
r#"{
|
||||||
|
"string": "Hello World",
|
||||||
|
"number": 10,
|
||||||
|
"array": [1, 2],
|
||||||
|
"boolean": true,
|
||||||
|
"null": null
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let flattened = FlattenedJson::from_raw(&raw);
|
||||||
|
assert_eq!(flattened.map, btreemap! { "string".into() => "Hello World".into() });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flattened_json_nested() {
|
||||||
|
let raw = serde_json::from_str::<Raw<JsonValue>>(
|
||||||
|
r#"{
|
||||||
|
"desc": "Level 0",
|
||||||
|
"desc.bis": "Level 0 bis",
|
||||||
|
"up": {
|
||||||
|
"desc": "Level 1",
|
||||||
|
"desc.bis": "Level 1 bis",
|
||||||
|
"up": {
|
||||||
|
"desc": "Level 2",
|
||||||
|
"desc\\bis": "Level 2 bis"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let flattened = FlattenedJson::from_raw(&raw);
|
||||||
|
assert_eq!(
|
||||||
|
flattened.map,
|
||||||
|
btreemap! {
|
||||||
|
"desc".into() => "Level 0".into(),
|
||||||
|
r"desc\.bis".into() => "Level 0 bis".into(),
|
||||||
|
"up.desc".into() => "Level 1".into(),
|
||||||
|
r"up.desc\.bis".into() => "Level 1 bis".into(),
|
||||||
|
"up.up.desc".into() => "Level 2".into(),
|
||||||
|
r"up.up.desc\\bis".into() => "Level 2 bis".into(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user