Make TryFrom<Element> chainable
This allows constructs like:
```rust
let residual = match Iq::try_from(stanza) {
Ok(iq) => return handle_iq(..),
Err(Error::TypeMismatch(_, _, v)) => v,
Err(other) => return handle_parse_error(..),
};
let residual = match Message::try_from(stanza) {
..
};
let residual = ..
log::warn!("unhandled object: {:?}", residual);
```
The interesting part of this is that this could be used in a loop over a
Vec<Box<dyn FnMut(Element) -> ControlFlow<SomeResult, Element>>, i.e. in
a parsing loop for a generic XML/XMPP stream.
The advantage is that the stanza.is() check runs only once (in
check_self!) and doesn't need to be duplicated outside, and it reduces
the use of magic strings.
This commit is contained in:
parent
3b3a4ff0c8
commit
2f7d5edb8a
8 changed files with 68 additions and 25 deletions
|
|
@ -37,7 +37,7 @@ macro_rules! generate_blocking_element {
|
||||||
check_no_attributes!(elem, $name);
|
check_no_attributes!(elem, $name);
|
||||||
let mut items = vec!();
|
let mut items = vec!();
|
||||||
for child in elem.children() {
|
for child in elem.children() {
|
||||||
check_self!(child, "item", BLOCKING);
|
check_child!(child, "item", BLOCKING);
|
||||||
check_no_unknown_attributes!(child, "item", ["jid"]);
|
check_no_unknown_attributes!(child, "item", ["jid"]);
|
||||||
check_no_children!(child, "item");
|
check_no_children!(child, "item");
|
||||||
items.push(get_attr!(child, "jid", Required));
|
items.push(get_attr!(child, "jid", Required));
|
||||||
|
|
|
||||||
|
|
@ -69,12 +69,12 @@ mod tests {
|
||||||
let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
|
let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let error = Delay::try_from(elem).unwrap_err();
|
let error = Delay::try_from(elem.clone()).unwrap_err();
|
||||||
let message = match error {
|
let returned_elem = match error {
|
||||||
Error::ParseError(string) => string,
|
Error::TypeMismatch(_, _, elem) => elem,
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
assert_eq!(message, "This is not a delay element.");
|
assert_eq!(elem, returned_elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -59,12 +59,12 @@ mod tests {
|
||||||
let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
|
let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let error = ExplicitMessageEncryption::try_from(elem).unwrap_err();
|
let error = ExplicitMessageEncryption::try_from(elem.clone()).unwrap_err();
|
||||||
let message = match error {
|
let returned_elem = match error {
|
||||||
Error::ParseError(string) => string,
|
Error::TypeMismatch(_, _, elem) => elem,
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
assert_eq!(message, "This is not a encryption element.");
|
assert_eq!(elem, returned_elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -253,12 +253,12 @@ mod tests {
|
||||||
let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
|
let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let error = Hash::try_from(elem).unwrap_err();
|
let error = Hash::try_from(elem.clone()).unwrap_err();
|
||||||
let message = match error {
|
let returned_elem = match error {
|
||||||
Error::ParseError(string) => string,
|
Error::TypeMismatch(_, _, elem) => elem,
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
assert_eq!(message, "This is not a hash element.");
|
assert_eq!(elem, returned_elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,7 @@ impl TryFrom<Element> for Checksum {
|
||||||
"JingleFT checksum element must have exactly one child.",
|
"JingleFT checksum element must have exactly one child.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
file = Some(File::try_from(child.clone())?);
|
file = Some(File::try_from(child.clone()).map_err(|e| e.hide_type_mismatch())?);
|
||||||
}
|
}
|
||||||
if file.is_none() {
|
if file.is_none() {
|
||||||
return Err(Error::ParseError(
|
return Err(Error::ParseError(
|
||||||
|
|
@ -541,9 +541,9 @@ mod tests {
|
||||||
let error = Checksum::try_from(elem).unwrap_err();
|
let error = Checksum::try_from(elem).unwrap_err();
|
||||||
let message = match error {
|
let message = match error {
|
||||||
Error::ParseError(string) => string,
|
Error::ParseError(string) => string,
|
||||||
_ => panic!(),
|
other => panic!("unexpected error: {:?}", other),
|
||||||
};
|
};
|
||||||
assert_eq!(message, "This is not a file element.");
|
assert_eq!(message, "Unexpected child element");
|
||||||
|
|
||||||
let elem: Element = "<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:5' creator='initiator'><file><hash xmlns='urn:xmpp:hashes:2' algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash></file></checksum>".parse().unwrap();
|
let elem: Element = "<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:5' creator='initiator'><file><hash xmlns='urn:xmpp:hashes:2' algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash></file></checksum>".parse().unwrap();
|
||||||
let error = Checksum::try_from(elem).unwrap_err();
|
let error = Checksum::try_from(elem).unwrap_err();
|
||||||
|
|
|
||||||
|
|
@ -213,22 +213,22 @@ mod tests {
|
||||||
let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
|
let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let error = SetQuery::try_from(elem).unwrap_err();
|
let error = SetQuery::try_from(elem.clone()).unwrap_err();
|
||||||
let message = match error {
|
let returned_elem = match error {
|
||||||
Error::ParseError(string) => string,
|
Error::TypeMismatch(_, _, elem) => elem,
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
assert_eq!(message, "This is not a RSM set element.");
|
assert_eq!(elem, returned_elem);
|
||||||
|
|
||||||
let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
|
let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>"
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let error = SetResult::try_from(elem).unwrap_err();
|
let error = SetResult::try_from(elem.clone()).unwrap_err();
|
||||||
let message = match error {
|
let returned_elem = match error {
|
||||||
Error::ParseError(string) => string,
|
Error::TypeMismatch(_, _, elem) => elem,
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
assert_eq!(message, "This is not a RSM set element.");
|
assert_eq!(elem, returned_elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,12 @@ pub enum Error {
|
||||||
/// of a freeform string.
|
/// of a freeform string.
|
||||||
ParseError(&'static str),
|
ParseError(&'static str),
|
||||||
|
|
||||||
|
/// Element local-name/namespace mismatch
|
||||||
|
///
|
||||||
|
/// Returns the original element unaltered, as well as the expected ns and
|
||||||
|
/// local-name.
|
||||||
|
TypeMismatch(&'static str, &'static str, crate::Element),
|
||||||
|
|
||||||
/// Generated when some base64 content fails to decode, usually due to
|
/// Generated when some base64 content fails to decode, usually due to
|
||||||
/// extra characters.
|
/// extra characters.
|
||||||
Base64Error(base64::DecodeError),
|
Base64Error(base64::DecodeError),
|
||||||
|
|
@ -40,10 +46,24 @@ pub enum Error {
|
||||||
ChronoParseError(chrono::ParseError),
|
ChronoParseError(chrono::ParseError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
/// Converts the TypeMismatch error to a generic ParseError
|
||||||
|
///
|
||||||
|
/// This must be used when TryFrom is called on children to avoid confusing
|
||||||
|
/// user code which assumes that TypeMismatch refers to the top level
|
||||||
|
/// element only.
|
||||||
|
pub(crate) fn hide_type_mismatch(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Error::TypeMismatch(..) => Error::ParseError("Unexpected child element"),
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl StdError for Error {
|
impl StdError for Error {
|
||||||
fn cause(&self) -> Option<&dyn StdError> {
|
fn cause(&self) -> Option<&dyn StdError> {
|
||||||
match self {
|
match self {
|
||||||
Error::ParseError(_) => None,
|
Error::ParseError(_) | Error::TypeMismatch(..) => None,
|
||||||
Error::Base64Error(e) => Some(e),
|
Error::Base64Error(e) => Some(e),
|
||||||
Error::ParseIntError(e) => Some(e),
|
Error::ParseIntError(e) => Some(e),
|
||||||
Error::ParseStringError(e) => Some(e),
|
Error::ParseStringError(e) => Some(e),
|
||||||
|
|
@ -58,6 +78,14 @@ impl fmt::Display for Error {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Error::ParseError(s) => write!(fmt, "parse error: {}", s),
|
Error::ParseError(s) => write!(fmt, "parse error: {}", s),
|
||||||
|
Error::TypeMismatch(ns, localname, element) => write!(
|
||||||
|
fmt,
|
||||||
|
"element type mismatch: expected {{{}}}{}, got {{{}}}{}",
|
||||||
|
ns,
|
||||||
|
localname,
|
||||||
|
element.ns(),
|
||||||
|
element.name()
|
||||||
|
),
|
||||||
Error::Base64Error(e) => write!(fmt, "base64 error: {}", e),
|
Error::Base64Error(e) => write!(fmt, "base64 error: {}", e),
|
||||||
Error::ParseIntError(e) => write!(fmt, "integer parsing error: {}", e),
|
Error::ParseIntError(e) => write!(fmt, "integer parsing error: {}", e),
|
||||||
Error::ParseStringError(e) => write!(fmt, "string parsing error: {}", e),
|
Error::ParseStringError(e) => write!(fmt, "string parsing error: {}", e),
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,21 @@ macro_rules! check_self {
|
||||||
($elem:ident, $name:tt, $ns:ident) => {
|
($elem:ident, $name:tt, $ns:ident) => {
|
||||||
check_self!($elem, $name, $ns, $name);
|
check_self!($elem, $name, $ns, $name);
|
||||||
};
|
};
|
||||||
|
($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => {
|
||||||
|
if !$elem.is($name, crate::ns::$ns) {
|
||||||
|
return Err(crate::util::error::Error::TypeMismatch(
|
||||||
|
$name,
|
||||||
|
crate::ns::$ns,
|
||||||
|
$elem,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! check_child {
|
||||||
|
($elem:ident, $name:tt, $ns:ident) => {
|
||||||
|
check_child!($elem, $name, $ns, $name);
|
||||||
|
};
|
||||||
($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => {
|
($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => {
|
||||||
if !$elem.is($name, crate::ns::$ns) {
|
if !$elem.is($name, crate::ns::$ns) {
|
||||||
return Err(crate::util::error::Error::ParseError(concat!(
|
return Err(crate::util::error::Error::ParseError(concat!(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue