cargo fmt

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
Maxime “pep” Buquet 2023-04-18 17:48:22 +02:00
parent c847ceb3bf
commit 6ede9da169
Signed by: pep
GPG key ID: DEDA74AEECA9D0F2
3 changed files with 237 additions and 190 deletions

View file

@ -35,17 +35,17 @@
//! # mode to ensure that it only matches a completely empty message stanza: //! # mode to ensure that it only matches a completely empty message stanza:
//! //!
//! Louise receives: //! Louise receives:
//! <message scansion:strict="true"/> //! <message scansion:strict="true"/>
//! ``` //! ```
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Deref;
use std::fmt::Debug; use std::fmt::Debug;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::Deref;
use crate::{Client, ClientName};
use crate::types::VariableAttr;
use crate::parsers::parse_variable; use crate::parsers::parse_variable;
use crate::types::VariableAttr;
use crate::{Client, ClientName};
use jid::BareJid; use jid::BareJid;
use minidom::{Element, Node}; use minidom::{Element, Node};
@ -84,12 +84,8 @@ impl ScanNode {
impl PartialEq<Node> for ScanNode { impl PartialEq<Node> for ScanNode {
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)) => { (Node::Text(text1), Node::Text(text2)) => text1 == text2,
text1 == text2 (Node::Element(elem1), Node::Element(elem2)) => ScanElement::new(&elem1) == elem2,
},
(Node::Element(elem1), Node::Element(elem2)) => {
ScanElement::new(&elem1) == elem2
},
_ => false, _ => false,
} }
} }
@ -116,46 +112,46 @@ fn filter_whitespace_nodes(nodes: Vec<Node>) -> Vec<Node> {
(Some(type_), acc) (Some(type_), acc)
}; };
let rm_empty_text = |node: &Node| { let rm_empty_text = |node: &Node| match node {
match node { Node::Text(text) => text.trim().len() != 0,
Node::Text(text) => { _ => true,
text.trim().len() != 0
},
_ => true,
}
}; };
let nodes = nodes let nodes = nodes
.into_iter() .into_iter()
.fold((None::<NodeType>, vec![]), filter_nodes).1; .fold((None::<NodeType>, vec![]), filter_nodes)
.1;
// Don't remove possibly significant whitespace text leaves // Don't remove possibly significant whitespace text leaves
if nodes.iter().count() == 1 { if nodes.iter().count() == 1 {
nodes nodes
} else { } else {
nodes nodes.into_iter().filter(rm_empty_text).collect()
.into_iter() }
.filter(rm_empty_text)
.collect()
}
} }
#[derive(Debug)] #[derive(Debug)]
struct ScanNodes<T: Debug> { struct ScanNodes<T: Debug> {
nodes: Vec<Node>, nodes: Vec<Node>,
_strict: PhantomData<T>, _strict: PhantomData<T>,
} }
impl ScanNodes<NonStrictComparison> { impl ScanNodes<NonStrictComparison> {
fn new(nodes: Vec<Node>) -> ScanNodes<NonStrictComparison> { fn new(nodes: Vec<Node>) -> ScanNodes<NonStrictComparison> {
Self { nodes, _strict: PhantomData } Self {
} nodes,
_strict: PhantomData,
}
}
} }
impl ScanNodes<StrictComparison> { impl ScanNodes<StrictComparison> {
fn new_strict(nodes: Vec<Node>) -> ScanNodes<StrictComparison> { fn new_strict(nodes: Vec<Node>) -> ScanNodes<StrictComparison> {
Self { nodes, _strict: PhantomData } Self {
} nodes,
_strict: PhantomData,
}
}
} }
/// 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
@ -164,9 +160,9 @@ impl ScanNodes<StrictComparison> {
impl PartialEq<Vec<Node>> for ScanNodes<StrictComparison> { impl PartialEq<Vec<Node>> for ScanNodes<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(ScanNode::new)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let filtered_other = filter_whitespace_nodes(other.clone()); let filtered_other = filter_whitespace_nodes(other.clone());
filtered_self == filtered_other filtered_self == filtered_other
@ -181,15 +177,15 @@ impl PartialEq<Vec<Node>> for ScanNodes<NonStrictComparison> {
let filtered_other = filter_whitespace_nodes(other.clone()); let filtered_other = filter_whitespace_nodes(other.clone());
filter_whitespace_nodes(self.nodes.clone()) filter_whitespace_nodes(self.nodes.clone())
.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);
res && res && filtered_other
filtered_other.iter().find(|onode| { .iter()
&&scan == onode .find(|onode| &&scan == onode)
}).is_some() .is_some()
}) })
} }
} }
@ -200,12 +196,12 @@ impl PartialEq<Vec<Node>> for ScanNodes<NonStrictComparison> {
/// Also uses the custom ScanNode implementation. /// Also uses the custom ScanNode implementation.
#[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 HashMap<ClientName, Client>>,
} }
impl<'a, 'b> Deref for ScanElement<'a, 'b> { impl<'a, 'b> Deref for ScanElement<'a, 'b> {
type Target = Element; type Target = Element;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.elem &self.elem
@ -213,24 +209,28 @@ impl<'a, 'b> Deref for ScanElement<'a, 'b> {
} }
impl<'a> ScanElement<'a, 'static> { impl<'a> ScanElement<'a, 'static> {
pub fn new(elem: &'a Element) -> ScanElement { pub fn new(elem: &'a Element) -> ScanElement {
Self { elem, context: None } Self {
} elem,
context: None,
}
}
} }
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: &'b HashMap<ClientName, Client>) -> ScanElement<'a, 'b> {
Self { elem: self.elem, context: Some(context) } Self {
} elem: self.elem,
context: Some(context),
}
}
} }
impl<'a, 'b> PartialEq<&Element> for ScanElement<'a, 'b> { impl<'a, 'b> PartialEq<&Element> for ScanElement<'a, 'b> {
fn eq(&self, other: &&Element) -> bool { fn eq(&self, other: &&Element) -> bool {
let self_ns = self.elem.ns(); let self_ns = self.elem.ns();
if self.elem.name() == other.name() && if self.elem.name() == other.name() && self_ns == other.ns() {
self_ns == other.ns() { let strict_attr = self.elem.attr("scansion:strict");
let strict_attr = self.elem.attr("scansion:strict");
// Force true if scansion:strict is set or if a tag isn't in the default ns. // Force true if scansion:strict is set or if a tag isn't in the default ns.
let strict = if let Some(val) = strict_attr { let strict = if let Some(val) = strict_attr {
@ -239,14 +239,14 @@ impl<'a, 'b> PartialEq<&Element> for ScanElement<'a, 'b> {
self_ns != DEFAULT_NS self_ns != DEFAULT_NS
}; };
for (attr, val) in self.elem.attrs() { for (attr, val) in self.elem.attrs() {
if val == "{scansion:any}" { if val == "{scansion:any}" {
continue; continue;
} }
// 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) {
@ -266,33 +266,33 @@ impl<'a, 'b> PartialEq<&Element> for ScanElement<'a, 'b> {
} }
} }
match (attr, other.attr(attr)) { match (attr, other.attr(attr)) {
(attr, _) if attr == "scansion:strict" => continue, (attr, _) if attr == "scansion:strict" => continue,
(_, None) => return false, (_, None) => return false,
(_, Some(oval)) if val != oval => return false, (_, Some(oval)) if val != oval => return false,
_ => (), _ => (),
} }
} }
let onodes = other.nodes().cloned().collect::<Vec<_>>(); let onodes = other.nodes().cloned().collect::<Vec<_>>();
// Compare attributes count // Compare attributes count
if strict { if strict {
let count = self.elem.attrs().into_iter().count(); let count = self.elem.attrs().into_iter().count();
let ocount = other.attrs().into_iter().count(); let ocount = other.attrs().into_iter().count();
match strict_attr { match strict_attr {
None if count != ocount => return false, None if count != ocount => return false,
Some(_) if count != ocount + 1 => return false, Some(_) if count != ocount + 1 => return false,
_ => (), _ => (),
} }
let nodes = ScanNodes::new_strict(self.elem.nodes().cloned().collect()); let nodes = ScanNodes::new_strict(self.elem.nodes().cloned().collect());
nodes == onodes nodes == onodes
} else { } else {
let nodes = ScanNodes::new(self.elem.nodes().cloned().collect()); let nodes = ScanNodes::new(self.elem.nodes().cloned().collect());
nodes == onodes nodes == onodes
} }
} else { } else {
false false
} }
@ -304,6 +304,8 @@ mod tests {
use super::*; use super::*;
use std::str::FromStr; use std::str::FromStr;
use jid::Jid;
#[test] #[test]
fn compare_nodes_simple() { fn compare_nodes_simple() {
let text1 = Node::Text(String::from("\t\t")); let text1 = Node::Text(String::from("\t\t"));
@ -360,7 +362,9 @@ mod tests {
#[test] #[test]
fn compare_element_non_strict_whitespace_success() { fn compare_element_non_strict_whitespace_success() {
let elem1: Element = "<presence xmlns='foo'>\n\t<foo/></presence>".parse().unwrap(); let elem1: Element = "<presence xmlns='foo'>\n\t<foo/></presence>"
.parse()
.unwrap();
let elem2: Element = "<presence xmlns='foo'><foo/></presence>".parse().unwrap(); let elem2: Element = "<presence xmlns='foo'><foo/></presence>".parse().unwrap();
let scan1 = ScanElement::new(&elem1); let scan1 = ScanElement::new(&elem1);
@ -369,8 +373,12 @@ mod tests {
#[test] #[test]
fn compare_element_non_strict_whitespace_failure() { fn compare_element_non_strict_whitespace_failure() {
let elem1: Element = "<presence scansion:strict='false' xmlns='foo'>\n\tfoo</presence>".parse().unwrap(); let elem1: Element = "<presence scansion:strict='false' xmlns='foo'>\n\tfoo</presence>"
let elem2: Element = "<presence xmlns='foo'>\n\tfoo\t</presence>".parse().unwrap(); .parse()
.unwrap();
let elem2: Element = "<presence xmlns='foo'>\n\tfoo\t</presence>"
.parse()
.unwrap();
let scan1 = ScanElement::new(&elem1); let scan1 = ScanElement::new(&elem1);
assert_ne!(scan1, &elem2); assert_ne!(scan1, &elem2);
@ -384,11 +392,15 @@ mod tests {
assert_eq!(scan1, &elem1); assert_eq!(scan1, &elem1);
let elem2: Element = "<presence scansion:strict='true' xmlns='jabber:client'> let elem2: Element = "<presence scansion:strict='true' xmlns='jabber:client'>
<x xmlns='http://jabber.org/protocol/muc'/> <x xmlns='http://jabber.org/protocol/muc'/>
</presence>".parse().unwrap(); </presence>"
.parse()
.unwrap();
let elem3: Element = "<presence xmlns='jabber:client'> let elem3: Element = "<presence xmlns='jabber:client'>
<x xmlns='http://jabber.org/protocol/muc'/> <x xmlns='http://jabber.org/protocol/muc'/>
</presence>".parse().unwrap(); </presence>"
.parse()
.unwrap();
let scan2 = ScanElement::new(&elem2); let scan2 = ScanElement::new(&elem2);
assert_eq!(scan2, &elem3); assert_eq!(scan2, &elem3);
@ -406,18 +418,22 @@ mod tests {
#[test] #[test]
fn compare_element_strict_nodes_success() { fn compare_element_strict_nodes_success() {
let elem1: Element = "<presence scansion:strict='true' xmlns='jabber:client'> let elem1: Element = "<presence scansion:strict='true' xmlns='jabber:client'>
<x xmlns='http://jabber.org/protocol/muc' /> <x xmlns='http://jabber.org/protocol/muc' />
<x xmlns='vcard-temp:x:update'> <x xmlns='vcard-temp:x:update'>
<photo/> <photo/>
</x> </x>
</presence>".parse().unwrap(); </presence>"
// The same, minus 'scansion:strict' .parse()
.unwrap();
// The same, minus 'scansion:strict'
let elem2: Element = "<presence xmlns='jabber:client'> let elem2: Element = "<presence xmlns='jabber:client'>
<x xmlns='http://jabber.org/protocol/muc' /> <x xmlns='http://jabber.org/protocol/muc' />
<x xmlns='vcard-temp:x:update'> <x xmlns='vcard-temp:x:update'>
<photo/> <photo/>
</x> </x>
</presence>".parse().unwrap(); </presence>"
.parse()
.unwrap();
let scan1 = ScanElement::new(&elem1); let scan1 = ScanElement::new(&elem1);
assert_eq!(scan1, &elem2); assert_eq!(scan1, &elem2);
@ -426,15 +442,19 @@ mod tests {
#[test] #[test]
fn compare_element_strict_nodes_failure() { fn compare_element_strict_nodes_failure() {
let elem1: Element = "<presence scansion:strict='true' xmlns='jabber:client'> let elem1: Element = "<presence scansion:strict='true' xmlns='jabber:client'>
<x xmlns='http://jabber.org/protocol/muc' /> <x xmlns='http://jabber.org/protocol/muc' />
<x xmlns='vcard-temp:x:update'> <x xmlns='vcard-temp:x:update'>
<photo/> <photo/>
</x> </x>
</presence>".parse().unwrap(); </presence>"
.parse()
.unwrap();
let elem2: Element = "<presence scansion:strict='true' xmlns='jabber:client'> let elem2: Element = "<presence scansion:strict='true' xmlns='jabber:client'>
<x xmlns='http://jabber.org/protocol/muc' /> <x xmlns='http://jabber.org/protocol/muc' />
<x xmlns='vcard-temp:x:update'/> <x xmlns='vcard-temp:x:update'/>
</presence>".parse().unwrap(); </presence>"
.parse()
.unwrap();
let scan1 = ScanElement::new(&elem1); let scan1 = ScanElement::new(&elem1);
assert_ne!(scan1, &elem2); assert_ne!(scan1, &elem2);
@ -442,17 +462,23 @@ mod tests {
#[test] #[test]
fn compare_element_non_strict_attributes_success() { fn compare_element_non_strict_attributes_success() {
let elem1: Element = "<presence scansion:strict='false' xmlns='foo'/>".parse().unwrap(); let elem1: Element = "<presence scansion:strict='false' xmlns='foo'/>"
.parse()
.unwrap();
let scan1 = ScanElement::new(&elem1); let scan1 = ScanElement::new(&elem1);
assert_eq!(scan1, &elem1); assert_eq!(scan1, &elem1);
let elem2: Element = "<presence xmlns='jabber:client'> let elem2: Element = "<presence xmlns='jabber:client'>
<x xmlns='http://jabber.org/protocol/muc' scansion:strict='false' /> <x xmlns='http://jabber.org/protocol/muc' scansion:strict='false' />
</presence>".parse().unwrap(); </presence>"
.parse()
.unwrap();
let elem3: Element = "<presence xmlns='jabber:client' foo='bar'> let elem3: Element = "<presence xmlns='jabber:client' foo='bar'>
<x xmlns='http://jabber.org/protocol/muc' baz='qxx' /> <x xmlns='http://jabber.org/protocol/muc' baz='qxx' />
</presence>".parse().unwrap(); </presence>"
.parse()
.unwrap();
let scan2 = ScanElement::new(&elem2); let scan2 = ScanElement::new(&elem2);
assert_eq!(scan2, &elem3); assert_eq!(scan2, &elem3);
@ -460,18 +486,24 @@ mod tests {
#[test] #[test]
fn compare_element_non_strict_attributes_failure() { fn compare_element_non_strict_attributes_failure() {
let elem1: Element = "<presence scansion:strict='false' foo='bar' xmlns='foo'/>".parse().unwrap(); let elem1: Element = "<presence scansion:strict='false' foo='bar' xmlns='foo'/>"
.parse()
.unwrap();
let elem2: Element = "<presence xmlns='foo' />".parse().unwrap(); let elem2: Element = "<presence xmlns='foo' />".parse().unwrap();
let scan1 = ScanElement::new(&elem1); let scan1 = ScanElement::new(&elem1);
assert_ne!(scan1, &elem2); assert_ne!(scan1, &elem2);
let elem2: Element = "<presence xmlns='jabber:client' foo='bar'> let elem2: Element = "<presence xmlns='jabber:client' foo='bar'>
<x xmlns='http://jabber.org/protocol/muc'/> <x xmlns='http://jabber.org/protocol/muc'/>
</presence>".parse().unwrap(); </presence>"
.parse()
.unwrap();
let elem3: Element = "<presence xmlns='jabber:client'> let elem3: Element = "<presence xmlns='jabber:client'>
<x xmlns='http://jabber.org/protocol/muc'/> <x xmlns='http://jabber.org/protocol/muc'/>
</presence>".parse().unwrap(); </presence>"
.parse()
.unwrap();
let scan2 = ScanElement::new(&elem2); let scan2 = ScanElement::new(&elem2);
assert_ne!(scan2, &elem3); assert_ne!(scan2, &elem3);
@ -479,15 +511,19 @@ mod tests {
#[test] #[test]
fn compare_element_non_strict_elem_success() { fn compare_element_non_strict_elem_success() {
let elem1: Element = "<presence scansion:strict='false' xmlns='foo'/>".parse().unwrap(); let elem1: Element = "<presence scansion:strict='false' xmlns='foo'/>"
.parse()
.unwrap();
let elem2: Element = "<presence xmlns='foo'><foo/></presence>".parse().unwrap(); let elem2: Element = "<presence xmlns='foo'><foo/></presence>".parse().unwrap();
let scan1 = ScanElement::new(&elem1); let scan1 = ScanElement::new(&elem1);
assert_eq!(scan1, &elem2); assert_eq!(scan1, &elem2);
// 'jabber:client' is non strict by default // 'jabber:client' is non strict by default
let elem3: Element = "<presence xmlns='jabber:client'/>".parse().unwrap(); let elem3: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
let elem4: Element = "<presence xmlns='jabber:client'><foo/></presence>".parse().unwrap(); let elem4: Element = "<presence xmlns='jabber:client'><foo/></presence>"
.parse()
.unwrap();
let scan3 = ScanElement::new(&elem3); let scan3 = ScanElement::new(&elem3);
assert_eq!(scan3, &elem4); assert_eq!(scan3, &elem4);
@ -496,54 +532,66 @@ mod tests {
#[test] #[test]
fn compare_element_non_strict_elem_failure() { fn compare_element_non_strict_elem_failure() {
let elem2: Element = "<message scansion:strict='false' xmlns='jabber:client'> let elem2: Element = "<message scansion:strict='false' xmlns='jabber:client'>
<body>foo</body> <body>foo</body>
</message>".parse().unwrap(); </message>"
.parse()
.unwrap();
let elem3: Element = "<message xmlns='jabber:client'> let elem3: Element = "<message xmlns='jabber:client'>
<body>bar</body> <body>bar</body>
</message>".parse().unwrap(); </message>"
.parse()
.unwrap();
let scan2 = ScanElement::new(&elem2); let scan2 = ScanElement::new(&elem2);
assert_ne!(scan2, &elem3); assert_ne!(scan2, &elem3);
} }
#[test] #[test]
fn compare_element_propagate_strictness() { fn compare_element_propagate_strictness() {
let elem1: Element = "<message scansion:strict='true' xmlns='jabber:client'> let elem1: Element = "<message scansion:strict='true' xmlns='jabber:client'>
<foo><bar /></foo> <foo><bar /></foo>
</message>".parse().unwrap(); </message>"
.parse()
.unwrap();
let elem2: Element = "<message scansion:strict='true' xmlns='jabber:client'> let elem2: Element = "<message scansion:strict='true' xmlns='jabber:client'>
<foo><bar baz='qxx' /></foo> <foo><bar baz='qxx' /></foo>
</message>".parse().unwrap(); </message>"
.parse()
.unwrap();
let scan1 = ScanElement::new(&elem1); let scan1 = ScanElement::new(&elem1);
assert_ne!(scan1, &elem2); assert_ne!(scan1, &elem2);
} }
#[test] #[test]
fn ignore_attr_val_success() { fn ignore_attr_val_success() {
let elem1: Element = "<message scansion:strict='true' xmlns='jabber:client' id='{scansion:any}' />" let elem1: Element = "<message xmlns='foo' id='{scansion:any}' />"
.parse().unwrap(); .parse()
let elem2: Element = "<message xmlns='jabber:client' id='some-id' />".parse().unwrap(); .unwrap();
let scan1 = ScanElement::new(&elem1); let elem2: Element = "<message xmlns='foo' id='some-id' />".parse().unwrap();
let scan1 = ScanElement::new(&elem1);
assert_eq!(scan1, &elem2); assert_eq!(scan1, &elem2);
} }
#[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 clients = { let clients = {
let mut tmp = HashMap::new(); let mut tmp = HashMap::new();
tmp.insert(String::from("louise"), louise); tmp.insert(String::from("louise"), louise);
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().unwrap(); .parse()
let elem2: Element = "<message xmlns='foo' to='louise@example.com' />".parse().unwrap(); .unwrap();
let scan1 = ScanElement::new(&elem1).with_context(&clients); let elem2: Element = "<message xmlns='foo' to='louise@example.com' />"
.parse()
.unwrap();
let scan1 = ScanElement::new(&elem1).with_context(&clients);
assert_eq!(scan1, &elem2); assert_eq!(scan1, &elem2);
} }
} }

View file

@ -4,7 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::types::{Action, Client, ClientName, Metadata, VariableAttr, Spec}; use crate::types::{Action, Client, ClientName, Metadata, Spec, VariableAttr};
use std::collections::HashMap; use std::collections::HashMap;
use std::str::FromStr; use std::str::FromStr;
@ -280,22 +280,21 @@ pub fn parse_spec(i: &str) -> Result<Spec, Token> {
} }
pub fn parse_variable(s: Span) -> IResult<Span, VariableAttr> { pub fn parse_variable(s: Span) -> IResult<Span, VariableAttr> {
let (s, (_, name, attr, _)) = tuple(( let (s, (_, name, attr, _)) = tuple((
tag("${"), take_until_tags(vec![ tag("${"),
"'s full JID", take_until_tags(vec!["'s full JID", "'s JID"].into_iter(), "}"),
"'s JID", alt((tag("'s full JID"), tag("'s JID"))),
].into_iter(), tag("}"),
"}", ))(s)?;
),
alt((tag("'s full JID"), tag("'s JID"))),
tag("}"),
))(s)?;
Ok((s, match *attr.fragment() { Ok((
"'s full JID" => VariableAttr::FullJid(name.to_string()), s,
"'s JID" => VariableAttr::BareJid(name.to_string()), match *attr.fragment() {
_ => unreachable!(), "'s full JID" => VariableAttr::FullJid(name.to_string()),
})) "'s JID" => VariableAttr::BareJid(name.to_string()),
_ => unreachable!(),
},
))
} }
#[cfg(test)] #[cfg(test)]
@ -622,28 +621,28 @@ louise receives:
); );
} }
#[test] #[test]
fn parse_variable_attr() { fn parse_variable_attr() {
let buf1: Span = "${louise's full JID}".into(); let buf1: Span = "${louise's full JID}".into();
let buf2: Span = "${louise's JID}".into(); let buf2: Span = "${louise's JID}".into();
let buf3: Span = "${louise's JID".into(); let buf3: Span = "${louise's JID".into();
assert_eq!( assert_eq!(
parse_variable(buf1).unwrap().1, parse_variable(buf1).unwrap().1,
VariableAttr::FullJid(String::from("louise")), VariableAttr::FullJid(String::from("louise")),
); );
assert_eq!( assert_eq!(
parse_variable(buf2).unwrap().1, parse_variable(buf2).unwrap().1,
VariableAttr::BareJid(String::from("louise")), VariableAttr::BareJid(String::from("louise")),
); );
match parse_variable(buf3) { match parse_variable(buf3) {
Err(nom::Err::Error(nom::error::Error { input, .. })) => { Err(nom::Err::Error(nom::error::Error { input, .. })) => {
assert_eq!(input.location_offset(), 2); assert_eq!(input.location_offset(), 2);
assert_eq!(input.location_line(), 1); assert_eq!(input.location_line(), 1);
} }
err => panic!("Expected Err, found: {err:?}"), err => panic!("Expected Err, found: {err:?}"),
} }
} }
} }

View file

@ -10,8 +10,8 @@ use jid::Jid;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum VariableAttr { pub enum VariableAttr {
FullJid(ClientName), FullJid(ClientName),
BareJid(ClientName), BareJid(ClientName),
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]