ScanElement: Propagate context
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
parent
6ede9da169
commit
af9e47f0a8
1 changed files with 111 additions and 40 deletions
151
src/element.rs
151
src/element.rs
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue