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:
Jonas Schäfer 2024-03-02 09:19:49 +01:00 committed by Link Mauve
commit 2f7d5edb8a
8 changed files with 68 additions and 25 deletions

View file

@ -17,6 +17,12 @@ pub enum Error {
/// of a freeform string.
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
/// extra characters.
Base64Error(base64::DecodeError),
@ -40,10 +46,24 @@ pub enum Error {
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 {
fn cause(&self) -> Option<&dyn StdError> {
match self {
Error::ParseError(_) => None,
Error::ParseError(_) | Error::TypeMismatch(..) => None,
Error::Base64Error(e) => Some(e),
Error::ParseIntError(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 {
match self {
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::ParseIntError(e) => write!(fmt, "integer parsing error: {}", e),
Error::ParseStringError(e) => write!(fmt, "string parsing error: {}", e),