ScanElement: Propagate context

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
Maxime “pep” Buquet 2023-04-19 13:21:03 +02:00
parent 6ede9da169
commit af9e47f0a8
Signed by: pep
GPG key ID: DEDA74AEECA9D0F2

View file

@ -56,6 +56,8 @@ pub static DEFAULT_NS: &str = "jabber:client";
/// Namespace used for scansion attributes /// Namespace used for scansion attributes
pub static SCANSION_NS: &str = "https://matthewwild.co.uk/projects/scansion"; pub static SCANSION_NS: &str = "https://matthewwild.co.uk/projects/scansion";
pub type Context = HashMap<ClientName, Client>;
/// Strict Comparison marker /// Strict Comparison marker
#[derive(Debug)] #[derive(Debug)]
struct StrictComparison; struct StrictComparison;
@ -71,21 +73,24 @@ enum NodeType {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct ScanNode { struct ScanNode<'a> {
node: Node, node: Node,
context: Option<&'a Context>,
} }
impl ScanNode { impl<'a> ScanNode<'a> {
fn new(node: Node) -> ScanNode { fn new(node: Node, context: Option<&'a Context>) -> ScanNode {
ScanNode { node } ScanNode { node, context }
} }
} }
impl PartialEq<Node> for ScanNode { impl<'a> PartialEq<Node> for ScanNode<'a> {
fn eq(&self, other: &Node) -> bool { fn eq(&self, other: &Node) -> bool {
match (&self.node, other) { match (&self.node, other) {
(Node::Text(text1), Node::Text(text2)) => text1 == text2, (Node::Text(text1), Node::Text(text2)) => text1 == text2,
(Node::Element(elem1), Node::Element(elem2)) => ScanElement::new(&elem1) == elem2, (Node::Element(elem1), Node::Element(elem2)) => {
ScanElement::new(&elem1).with_context(self.context) == elem2
}
_ => false, _ => false,
} }
} }
@ -131,24 +136,49 @@ fn filter_whitespace_nodes(nodes: Vec<Node>) -> Vec<Node> {
} }
#[derive(Debug)] #[derive(Debug)]
struct ScanNodes<T: Debug> { struct ScanNodes<'a, T: Debug> {
nodes: Vec<Node>, nodes: Vec<Node>,
context: Option<&'a Context>,
_strict: PhantomData<T>, _strict: PhantomData<T>,
} }
impl ScanNodes<NonStrictComparison> { impl<'a> ScanNodes<'a, NonStrictComparison> {
fn new(nodes: Vec<Node>) -> ScanNodes<NonStrictComparison> { fn new(nodes: Vec<Node>) -> ScanNodes<'a, NonStrictComparison> {
Self { Self {
nodes, nodes,
context: None,
_strict: PhantomData,
}
}
fn new_with_context(
nodes: Vec<Node>,
context: Option<&'a Context>,
) -> ScanNodes<'a, NonStrictComparison> {
Self {
nodes,
context,
_strict: PhantomData, _strict: PhantomData,
} }
} }
} }
impl ScanNodes<StrictComparison> { impl<'a> ScanNodes<'a, StrictComparison> {
fn new_strict(nodes: Vec<Node>) -> ScanNodes<StrictComparison> { fn new_strict(nodes: Vec<Node>) -> ScanNodes<'a, StrictComparison> {
Self { Self {
nodes, nodes,
context: None,
_strict: PhantomData,
}
}
fn new_strict_with_context(
nodes: Vec<Node>,
context: Option<&'a Context>,
) -> ScanNodes<'a, StrictComparison> {
Self {
nodes,
context,
_strict: PhantomData, _strict: PhantomData,
} }
} }
@ -157,11 +187,11 @@ impl ScanNodes<StrictComparison> {
/// Tags with mixed significant text and children tags aren't valid in XMPP, so we know we can /// Tags with mixed significant text and children tags aren't valid in XMPP, so we know we can
/// remove them. Text leaves are compared as is. When comparing strictly, elements must be exactly the /// remove them. Text leaves are compared as is. When comparing strictly, elements must be exactly the
/// same. /// same.
impl PartialEq<Vec<Node>> for ScanNodes<StrictComparison> { impl<'a> PartialEq<Vec<Node>> for ScanNodes<'a, StrictComparison> {
fn eq(&self, other: &Vec<Node>) -> bool { fn eq(&self, other: &Vec<Node>) -> bool {
let filtered_self = filter_whitespace_nodes(self.nodes.clone()) let filtered_self = filter_whitespace_nodes(self.nodes.clone())
.into_iter() .into_iter()
.map(ScanNode::new) .map(|node| ScanNode::new(node, self.context))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let filtered_other = filter_whitespace_nodes(other.clone()); let filtered_other = filter_whitespace_nodes(other.clone());
@ -172,7 +202,7 @@ impl PartialEq<Vec<Node>> for ScanNodes<StrictComparison> {
/// Tags with mixed significant text and children tags aren't valid in XMPP, so we know we can /// Tags with mixed significant text and children tags aren't valid in XMPP, so we know we can
/// remove them. Text leaves are compared as is. When doing non-strict comparison, the target /// remove them. Text leaves are compared as is. When doing non-strict comparison, the target
/// element must have all attributes and children of the test element but it can have more. /// element must have all attributes and children of the test element but it can have more.
impl PartialEq<Vec<Node>> for ScanNodes<NonStrictComparison> { impl<'a> PartialEq<Vec<Node>> for ScanNodes<'a, NonStrictComparison> {
fn eq(&self, other: &Vec<Node>) -> bool { fn eq(&self, other: &Vec<Node>) -> bool {
let filtered_other = filter_whitespace_nodes(other.clone()); let filtered_other = filter_whitespace_nodes(other.clone());
@ -180,7 +210,7 @@ impl PartialEq<Vec<Node>> for ScanNodes<NonStrictComparison> {
.into_iter() .into_iter()
// Maps nodes to their comparison result // Maps nodes to their comparison result
.fold(true, |res, node| { .fold(true, |res, node| {
let scan = ScanNode::new(node); let scan = ScanNode::new(node, self.context);
res && filtered_other res && filtered_other
.iter() .iter()
.find(|onode| &&scan == onode) .find(|onode| &&scan == onode)
@ -197,7 +227,7 @@ impl PartialEq<Vec<Node>> for ScanNodes<NonStrictComparison> {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ScanElement<'a, 'b> { pub struct ScanElement<'a, 'b> {
elem: &'a Element, elem: &'a Element,
context: Option<&'b HashMap<ClientName, Client>>, context: Option<&'b Context>,
} }
impl<'a, 'b> Deref for ScanElement<'a, 'b> { impl<'a, 'b> Deref for ScanElement<'a, 'b> {
@ -218,10 +248,10 @@ impl<'a> ScanElement<'a, 'static> {
} }
impl<'a, 'b> ScanElement<'a, 'b> { impl<'a, 'b> ScanElement<'a, 'b> {
pub fn with_context(self, context: &'b HashMap<ClientName, Client>) -> ScanElement<'a, 'b> { pub fn with_context(self, context: Option<&'b Context>) -> ScanElement<'a, 'b> {
Self { Self {
elem: self.elem, elem: self.elem,
context: Some(context), context,
} }
} }
} }
@ -247,24 +277,24 @@ impl<'a, 'b> PartialEq<&Element> for ScanElement<'a, 'b> {
// Parse variables. If parsing fails, continue attr comparison. // Parse variables. If parsing fails, continue attr comparison.
// If context isn't set, skip this and continue attr comparison. // If context isn't set, skip this and continue attr comparison.
if let Ok((_, var)) = parse_variable(val.into()) && if let Ok((_, var)) = parse_variable(val.into()) &&
let Some(context) = self.context { let Some(context) = self.context {
let res = match var { let res = match var {
VariableAttr::FullJid(name) => match context.get(&name) { VariableAttr::FullJid(name) => match context.get(&name) {
Some(Client { jid, .. }) => String::from(jid.clone()), Some(Client { jid, .. }) => String::from(jid.clone()),
_ => return false, _ => return false,
}, },
VariableAttr::BareJid(name) => match context.get(&name) { VariableAttr::BareJid(name) => match context.get(&name) {
Some(Client { jid, .. }) => String::from(BareJid::from(jid.clone())), Some(Client { jid, .. }) => String::from(BareJid::from(jid.clone())),
_ => return false, _ => return false,
}, },
}; };
if let Some(oval) = other.attr(attr) && res == oval { if let Some(oval) = other.attr(attr) && res == oval {
continue; continue;
} else { } else {
return false; return false;
} }
} }
match (attr, other.attr(attr)) { match (attr, other.attr(attr)) {
(attr, _) if attr == "scansion:strict" => continue, (attr, _) if attr == "scansion:strict" => continue,
@ -287,10 +317,14 @@ impl<'a, 'b> PartialEq<&Element> for ScanElement<'a, 'b> {
_ => (), _ => (),
} }
let nodes = ScanNodes::new_strict(self.elem.nodes().cloned().collect()); let nodes = ScanNodes::new_strict_with_context(
self.elem.nodes().cloned().collect(),
self.context,
);
nodes == onodes nodes == onodes
} else { } else {
let nodes = ScanNodes::new(self.elem.nodes().cloned().collect()); let nodes =
ScanNodes::new_with_context(self.elem.nodes().cloned().collect(), self.context);
nodes == onodes nodes == onodes
} }
} else { } else {
@ -577,12 +611,14 @@ mod tests {
#[test] #[test]
fn variables_from_context() { fn variables_from_context() {
let louise = Client::new(Jid::from_str("louise@example.com").unwrap(), "passwd"); let louise = Client::new(Jid::from_str("louise@example.com").unwrap(), "passwd");
let rosa_phone = Client::new(Jid::from_str("rosa@example.com/phone").unwrap(), "passwd");
let clients = { let clients = Some({
let mut tmp = HashMap::new(); let mut tmp = HashMap::new();
tmp.insert(String::from("louise"), louise); tmp.insert(String::from("louise"), louise);
tmp.insert(String::from("rosa's phone"), rosa_phone);
tmp tmp
}; });
let elem1: Element = "<message xmlns='foo' to=\"${louise's full JID}\" />" let elem1: Element = "<message xmlns='foo' to=\"${louise's full JID}\" />"
.parse() .parse()
@ -590,7 +626,42 @@ mod tests {
let elem2: Element = "<message xmlns='foo' to='louise@example.com' />" let elem2: Element = "<message xmlns='foo' to='louise@example.com' />"
.parse() .parse()
.unwrap(); .unwrap();
let scan1 = ScanElement::new(&elem1).with_context(&clients); let scan1 = ScanElement::new(&elem1).with_context(clients.as_ref());
assert_eq!(scan1, &elem2);
let elem3: Element = "<message xmlns='foo' to=\"${rosa's phone's JID}\" />"
.parse()
.unwrap();
let elem4: Element = "<message xmlns='foo' to='rosa@example.com' />"
.parse()
.unwrap();
let scan3 = ScanElement::new(&elem3).with_context(clients.as_ref());
assert_eq!(scan3, &elem4);
}
#[test]
fn variables_propagate_context() {
let louise = Client::new(
Jid::from_str("louise@example.com/device1").unwrap(),
"passwd",
);
let clients = Some({
let mut tmp = HashMap::new();
tmp.insert(String::from("louise"), louise);
tmp
});
let elem1: Element = "<message xmlns='foo'><foo to=\"${louise's full JID}\" /></message>"
.parse()
.unwrap();
let elem2: Element =
"<message xmlns='foo'><foo to='louise@example.com/device1' /></message>"
.parse()
.unwrap();
let scan1 = ScanElement::new(&elem1).with_context(clients.as_ref());
assert_eq!(scan1, &elem2); assert_eq!(scan1, &elem2);
} }