This doesn't fix all of the clippy warnings in these crates. There are decisions that needs to be made in there that I'm not willing to make. Signed-off-by: pep <pep@bouah.net>
724 lines
25 KiB
Rust
724 lines
25 KiB
Rust
//! # Generic builder type implementations
|
|
//!
|
|
//! This module contains [`FromEventsBuilder`] implementations for types from
|
|
//! foreign libraries (such as the standard library).
|
|
//!
|
|
//! In order to not clutter the `xso` crate's main namespace, they are
|
|
//! stashed away in a separate module.
|
|
|
|
// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// 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/.
|
|
|
|
use alloc::boxed::Box;
|
|
|
|
use crate::error::{Error, FromEventsError};
|
|
use crate::{FromEventsBuilder, FromXml};
|
|
|
|
/// Match an XML element qualified name.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub enum XmlNameMatcher<'x> {
|
|
/// Match any XML element
|
|
Any,
|
|
|
|
/// Match any XML element in the given namespace.
|
|
InNamespace(&'x str),
|
|
|
|
/// Match any XML element with the exact namespace/name combination.
|
|
Specific(&'x str, &'x str),
|
|
}
|
|
|
|
impl<'x> XmlNameMatcher<'x> {
|
|
/// Return the superset of two `XmlNameMatcher` instances.
|
|
pub const fn superset(self, other: Self) -> Self {
|
|
match self {
|
|
Self::Any => Self::Any,
|
|
Self::InNamespace(my_namespace) => match other {
|
|
Self::Any => Self::Any,
|
|
Self::InNamespace(other_namespace) | Self::Specific(other_namespace, _) => {
|
|
if crate::util::const_str_eq(my_namespace, other_namespace) {
|
|
Self::InNamespace(my_namespace)
|
|
} else {
|
|
Self::Any
|
|
}
|
|
}
|
|
},
|
|
Self::Specific(my_namespace, my_name) => match other {
|
|
Self::Any => Self::Any,
|
|
Self::InNamespace(other_namespace) => {
|
|
if crate::util::const_str_eq(my_namespace, other_namespace) {
|
|
Self::InNamespace(my_namespace)
|
|
} else {
|
|
Self::Any
|
|
}
|
|
}
|
|
Self::Specific(other_namespace, other_name) => {
|
|
if crate::util::const_str_eq(my_namespace, other_namespace) {
|
|
if crate::util::const_str_eq(my_name, other_name) {
|
|
Self::Specific(my_name, other_name)
|
|
} else {
|
|
Self::InNamespace(my_namespace)
|
|
}
|
|
} else {
|
|
Self::Any
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Return true if the given `qname` matches this matcher.
|
|
pub fn matches(&self, qname: &rxml::QName) -> bool {
|
|
match self {
|
|
Self::Any => true,
|
|
Self::InNamespace(ns) => qname.0.as_str() == *ns,
|
|
Self::Specific(ns, name) => qname.0.as_str() == *ns && qname.1.as_str() == *name,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// # Parsing context for [`FromEventsBuilder`]
|
|
///
|
|
/// For the most part, [`FromEventsBuilder`] implementations can work with
|
|
/// only the information inside the [`rxml::Event`] which is delivered to
|
|
/// them (and any information they may have stored from previous events).
|
|
///
|
|
/// However, there is (currently) one special case: the `xml:lang` attribute.
|
|
/// That attribute is inherited across the entire document tree hierarchy. If
|
|
/// the parsed element is not the top-level element, there may be an implicit
|
|
/// value for `xml:lang`.
|
|
#[derive(Debug)]
|
|
#[doc(hidden)]
|
|
pub struct Context<'x> {
|
|
language: Option<&'x str>,
|
|
}
|
|
|
|
impl<'x> Context<'x> {
|
|
/// A context suitable for the beginning of the document.
|
|
///
|
|
/// `xml:lang` is assumed to be unset.
|
|
pub fn empty() -> Self {
|
|
Self { language: None }
|
|
}
|
|
|
|
/// Set the effective `xml:lang` value on the context and return it.
|
|
pub fn with_language(mut self, language: Option<&'x str>) -> Self {
|
|
self.language = language;
|
|
self
|
|
}
|
|
|
|
/// Return the `xml:lang` value in effect at the end of the event which
|
|
/// is currently being processed.
|
|
pub fn language(&self) -> Option<&str> {
|
|
self.language
|
|
}
|
|
}
|
|
|
|
/// Helper struct to construct an `Option<T>` from XML events.
|
|
pub struct OptionBuilder<T: FromEventsBuilder>(T);
|
|
|
|
impl<T: FromEventsBuilder> FromEventsBuilder for OptionBuilder<T> {
|
|
type Output = Option<T::Output>;
|
|
|
|
fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
|
|
self.0.feed(ev, ctx).map(|ok| ok.map(Some))
|
|
}
|
|
}
|
|
|
|
/// Parsers `T` into `Some(.)`.
|
|
///
|
|
/// Note that this never generates `None`: The main use case is to allow
|
|
/// external (i.e. without calling `from_events`) defaulting to `None` and
|
|
/// for optional serialisation (the [`AsXml`][`crate::AsXml`] implementation
|
|
/// on `Option<T>` emits nothing for `None`).
|
|
impl<T: FromXml> FromXml for Option<T> {
|
|
type Builder = OptionBuilder<T::Builder>;
|
|
|
|
fn from_events(
|
|
name: rxml::QName,
|
|
attrs: rxml::AttrMap,
|
|
ctx: &Context<'_>,
|
|
) -> Result<Self::Builder, FromEventsError> {
|
|
Ok(OptionBuilder(T::from_events(name, attrs, ctx)?))
|
|
}
|
|
}
|
|
|
|
/// Helper struct to construct an `Box<T>` from XML events.
|
|
pub struct BoxBuilder<T: FromEventsBuilder + ?Sized>(Box<T>);
|
|
|
|
impl<T: FromEventsBuilder + ?Sized> FromEventsBuilder for BoxBuilder<T> {
|
|
type Output = Box<T::Output>;
|
|
|
|
fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
|
|
self.0.feed(ev, ctx).map(|ok| ok.map(Box::new))
|
|
}
|
|
}
|
|
|
|
/// Parses `T` into a `Box`.
|
|
impl<T: FromXml + ?Sized> FromXml for Box<T> {
|
|
type Builder = BoxBuilder<T::Builder>;
|
|
|
|
fn from_events(
|
|
name: rxml::QName,
|
|
attrs: rxml::AttrMap,
|
|
ctx: &Context<'_>,
|
|
) -> Result<Self::Builder, FromEventsError> {
|
|
Ok(BoxBuilder(Box::new(T::from_events(name, attrs, ctx)?)))
|
|
}
|
|
}
|
|
|
|
impl<T: FromEventsBuilder + ?Sized> FromEventsBuilder for Box<T> {
|
|
type Output = T::Output;
|
|
|
|
fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
|
|
(**self).feed(ev, ctx)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum FallibleBuilderInner<T: FromEventsBuilder, E> {
|
|
Processing { depth: usize, builder: T },
|
|
Failed { depth: usize, err: Option<E> },
|
|
Done,
|
|
}
|
|
|
|
/// Build a `Result<T, E>` from XML.
|
|
///
|
|
/// This builder, invoked generally via the [`FromXml`] implementation on
|
|
/// `Result<T, E> where T: FromXml, E: From<Error>`, allows to fallably parse
|
|
/// an XSO from XML.
|
|
///
|
|
/// If an error occurs while parsing the XSO, the remaining events which
|
|
/// belong to that XSO are discarded. Once all events have been seen, the
|
|
/// error is returned as `Err(.)` value.
|
|
///
|
|
/// If parsing succeeds, the parsed XSO is returned as `Ok(.)` value.
|
|
#[derive(Debug)]
|
|
pub struct FallibleBuilder<T: FromEventsBuilder, E>(FallibleBuilderInner<T, E>);
|
|
|
|
impl<T: FromEventsBuilder, E: From<Error>> FromEventsBuilder for FallibleBuilder<T, E> {
|
|
type Output = Result<T::Output, E>;
|
|
|
|
fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
|
|
match self.0 {
|
|
FallibleBuilderInner::Processing {
|
|
ref mut depth,
|
|
ref mut builder,
|
|
} => {
|
|
let new_depth = match ev {
|
|
rxml::Event::StartElement(..) => match depth.checked_add(1) {
|
|
// I *think* it is OK to return an err here
|
|
// instead of panicking. The reason is that anyone
|
|
// who intends to resume processing at the level
|
|
// of where we started to parse this thing in case
|
|
// of an error either has to:
|
|
// - Use this fallible implementation and rely on
|
|
// it capturing the error (which we don't in
|
|
// this case).
|
|
// - Or count the depth themselves, which will
|
|
// either fail in the same way, or they use a
|
|
// wider type (in which case it's ok).
|
|
None => {
|
|
self.0 = FallibleBuilderInner::Done;
|
|
return Err(Error::Other("maximum XML nesting depth exceeded"));
|
|
}
|
|
Some(v) => Some(v),
|
|
},
|
|
// In case of an element end, underflow means that we
|
|
// have reached the end of the XSO we wanted to process.
|
|
// We handle that case at the end of the outer match's
|
|
// body: Either we have returned a value then (good), or,
|
|
// if we reach the end there with a new_depth == None,
|
|
// something went horribly wrong (and we panic).
|
|
rxml::Event::EndElement(..) => depth.checked_sub(1),
|
|
|
|
// Text and XML declarations have no influence on parsing
|
|
// depth.
|
|
rxml::Event::XmlDeclaration(..) | rxml::Event::Text(..) => Some(*depth),
|
|
};
|
|
|
|
match builder.feed(ev, ctx) {
|
|
Ok(Some(v)) => {
|
|
self.0 = FallibleBuilderInner::Done;
|
|
return Ok(Some(Ok(v)));
|
|
}
|
|
Ok(None) => {
|
|
// continue processing in the next round.
|
|
}
|
|
Err(e) => {
|
|
// We are now officially failed ..
|
|
match new_depth {
|
|
// .. but we are not done yet, so enter the
|
|
// failure backtracking state.
|
|
Some(depth) => {
|
|
self.0 = FallibleBuilderInner::Failed {
|
|
depth,
|
|
err: Some(e.into()),
|
|
};
|
|
return Ok(None);
|
|
}
|
|
// .. and we are done with parsing, so we return
|
|
// the error as value.
|
|
None => {
|
|
self.0 = FallibleBuilderInner::Done;
|
|
return Ok(Some(Err(e.into())));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
*depth = match new_depth {
|
|
Some(v) => v,
|
|
None => unreachable!("fallible parsing continued beyond end of element"),
|
|
};
|
|
|
|
// Need more events.
|
|
Ok(None)
|
|
}
|
|
FallibleBuilderInner::Failed {
|
|
ref mut depth,
|
|
ref mut err,
|
|
} => {
|
|
*depth = match ev {
|
|
rxml::Event::StartElement(..) => match depth.checked_add(1) {
|
|
// See above for error return rationale.
|
|
None => {
|
|
self.0 = FallibleBuilderInner::Done;
|
|
return Err(Error::Other("maximum XML nesting depth exceeded"));
|
|
}
|
|
Some(v) => v,
|
|
},
|
|
rxml::Event::EndElement(..) => match depth.checked_sub(1) {
|
|
Some(v) => v,
|
|
None => {
|
|
// We are officially done, return a value, switch
|
|
// states, and be done with it.
|
|
let err = err.take().expect("fallible parsing somehow lost its error");
|
|
self.0 = FallibleBuilderInner::Done;
|
|
return Ok(Some(Err(err)));
|
|
}
|
|
},
|
|
|
|
// Text and XML declarations have no influence on parsing
|
|
// depth.
|
|
rxml::Event::XmlDeclaration(..) | rxml::Event::Text(..) => *depth,
|
|
};
|
|
|
|
// Need more events
|
|
Ok(None)
|
|
}
|
|
FallibleBuilderInner::Done => {
|
|
panic!("FromEventsBuilder called after it returned a value")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Parsers `T` fallibly. See [`FallibleBuilder`] for details.
|
|
impl<T: FromXml, E: From<Error>> FromXml for Result<T, E> {
|
|
type Builder = FallibleBuilder<T::Builder, E>;
|
|
|
|
fn from_events(
|
|
name: rxml::QName,
|
|
attrs: rxml::AttrMap,
|
|
ctx: &Context<'_>,
|
|
) -> Result<Self::Builder, FromEventsError> {
|
|
match T::from_events(name, attrs, ctx) {
|
|
Ok(builder) => Ok(FallibleBuilder(FallibleBuilderInner::Processing {
|
|
depth: 0,
|
|
builder,
|
|
})),
|
|
Err(FromEventsError::Mismatch { name, attrs }) => {
|
|
Err(FromEventsError::Mismatch { name, attrs })
|
|
}
|
|
Err(FromEventsError::Invalid(e)) => Ok(FallibleBuilder(FallibleBuilderInner::Failed {
|
|
depth: 0,
|
|
err: Some(e.into()),
|
|
})),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Builder which discards an entire child tree without inspecting the
|
|
/// contents.
|
|
#[derive(Debug, Default)]
|
|
pub struct Discard {
|
|
depth: usize,
|
|
}
|
|
|
|
impl Discard {
|
|
/// Create a new discarding builder.
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
}
|
|
|
|
impl FromEventsBuilder for Discard {
|
|
type Output = ();
|
|
|
|
fn feed(&mut self, ev: rxml::Event, _ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
|
|
match ev {
|
|
rxml::Event::StartElement(..) => {
|
|
self.depth = match self.depth.checked_add(1) {
|
|
Some(v) => v,
|
|
None => return Err(Error::Other("maximum XML nesting depth exceeded")),
|
|
};
|
|
Ok(None)
|
|
}
|
|
rxml::Event::EndElement(..) => match self.depth.checked_sub(1) {
|
|
None => Ok(Some(())),
|
|
Some(v) => {
|
|
self.depth = v;
|
|
Ok(None)
|
|
}
|
|
},
|
|
_ => Ok(None),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Builder which discards the contents (or raises on unexpected contents).
|
|
///
|
|
/// This builder is only to be used from within the proc macros and is not
|
|
/// stable, public API.
|
|
#[doc(hidden)]
|
|
#[cfg(feature = "macros")]
|
|
pub struct EmptyBuilder {
|
|
childerr: &'static str,
|
|
texterr: &'static str,
|
|
}
|
|
|
|
#[cfg(feature = "macros")]
|
|
impl FromEventsBuilder for EmptyBuilder {
|
|
type Output = ();
|
|
|
|
fn feed(&mut self, ev: rxml::Event, _ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
|
|
match ev {
|
|
rxml::Event::EndElement(..) => Ok(Some(())),
|
|
rxml::Event::StartElement(..) => Err(Error::Other(self.childerr)),
|
|
rxml::Event::Text(..) => Err(Error::Other(self.texterr)),
|
|
_ => Err(Error::Other(
|
|
"unexpected content in supposed-to-be-empty element",
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Precursor struct for [`EmptyBuilder`].
|
|
///
|
|
/// This struct is only to be used from within the proc macros and is not
|
|
/// stable, public API.
|
|
#[doc(hidden)]
|
|
#[cfg(feature = "macros")]
|
|
pub struct Empty {
|
|
pub attributeerr: &'static str,
|
|
pub childerr: &'static str,
|
|
pub texterr: &'static str,
|
|
}
|
|
|
|
#[cfg(feature = "macros")]
|
|
impl Empty {
|
|
pub fn start(self, attr: rxml::AttrMap) -> Result<EmptyBuilder, Error> {
|
|
if !attr.is_empty() {
|
|
return Err(Error::Other(self.attributeerr));
|
|
}
|
|
Ok(EmptyBuilder {
|
|
childerr: self.childerr,
|
|
texterr: self.texterr,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
use alloc::borrow::ToOwned;
|
|
use rxml::{parser::EventMetrics, Event, Namespace, NcName};
|
|
|
|
macro_rules! null_builder {
|
|
($name:ident for $output:ident) => {
|
|
#[derive(Debug)]
|
|
enum $name {}
|
|
|
|
impl FromEventsBuilder for $name {
|
|
type Output = $output;
|
|
|
|
fn feed(
|
|
&mut self,
|
|
_: Event,
|
|
_: &Context<'_>,
|
|
) -> Result<Option<Self::Output>, Error> {
|
|
unreachable!();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
null_builder!(AlwaysMismatchBuilder for AlwaysMismatch);
|
|
null_builder!(InitialErrorBuilder for InitialError);
|
|
|
|
#[derive(Debug)]
|
|
struct AlwaysMismatch;
|
|
|
|
impl FromXml for AlwaysMismatch {
|
|
type Builder = AlwaysMismatchBuilder;
|
|
|
|
fn from_events(
|
|
name: rxml::QName,
|
|
attrs: rxml::AttrMap,
|
|
_ctx: &Context<'_>,
|
|
) -> Result<Self::Builder, FromEventsError> {
|
|
Err(FromEventsError::Mismatch { name, attrs })
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct InitialError;
|
|
|
|
impl FromXml for InitialError {
|
|
type Builder = InitialErrorBuilder;
|
|
|
|
fn from_events(
|
|
_: rxml::QName,
|
|
_: rxml::AttrMap,
|
|
_: &Context<'_>,
|
|
) -> Result<Self::Builder, FromEventsError> {
|
|
Err(FromEventsError::Invalid(Error::Other("some error")))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct FailOnContentBuilder;
|
|
|
|
impl FromEventsBuilder for FailOnContentBuilder {
|
|
type Output = FailOnContent;
|
|
|
|
fn feed(&mut self, _: Event, _: &Context<'_>) -> Result<Option<Self::Output>, Error> {
|
|
Err(Error::Other("content error"))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct FailOnContent;
|
|
|
|
impl FromXml for FailOnContent {
|
|
type Builder = FailOnContentBuilder;
|
|
|
|
fn from_events(
|
|
_: rxml::QName,
|
|
_: rxml::AttrMap,
|
|
_: &Context<'_>,
|
|
) -> Result<Self::Builder, FromEventsError> {
|
|
Ok(FailOnContentBuilder)
|
|
}
|
|
}
|
|
|
|
fn qname() -> rxml::QName {
|
|
(Namespace::NONE, NcName::try_from("test").unwrap())
|
|
}
|
|
|
|
fn attrs() -> rxml::AttrMap {
|
|
rxml::AttrMap::new()
|
|
}
|
|
|
|
#[test]
|
|
fn fallible_builder_mismatch_passthrough() {
|
|
match Result::<AlwaysMismatch, Error>::from_events(qname(), attrs(), &Context::empty()) {
|
|
Err(FromEventsError::Mismatch { .. }) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn fallible_builder_initial_error_capture() {
|
|
let ctx = Context::empty();
|
|
let mut builder = match Result::<InitialError, Error>::from_events(qname(), attrs(), &ctx) {
|
|
Ok(v) => v,
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(
|
|
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
|
|
&ctx,
|
|
) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
|
|
Ok(Some(Err(Error::Other("some error")))) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn fallible_builder_initial_error_capture_allows_nested_stuff() {
|
|
let ctx = Context::empty();
|
|
let mut builder = match Result::<InitialError, Error>::from_events(qname(), attrs(), &ctx) {
|
|
Ok(v) => v,
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(
|
|
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
|
|
&ctx,
|
|
) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(
|
|
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
|
|
&ctx,
|
|
) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(
|
|
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
|
|
&ctx,
|
|
) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(
|
|
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
|
|
&ctx,
|
|
) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(
|
|
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
|
|
&ctx,
|
|
) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(
|
|
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
|
|
&ctx,
|
|
) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
|
|
Ok(Some(Err(Error::Other("some error")))) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn fallible_builder_content_error_capture() {
|
|
let ctx = Context::empty();
|
|
let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
|
|
{
|
|
Ok(v) => v,
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
|
|
Ok(Some(Err(Error::Other("content error")))) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn fallible_builder_content_error_capture_with_more_content() {
|
|
let ctx = Context::empty();
|
|
let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
|
|
{
|
|
Ok(v) => v,
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(
|
|
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
|
|
&ctx,
|
|
) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
|
|
Ok(Some(Err(Error::Other("content error")))) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn fallible_builder_content_error_capture_with_nested_content() {
|
|
let ctx = Context::empty();
|
|
let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
|
|
{
|
|
Ok(v) => v,
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(
|
|
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
|
|
&ctx,
|
|
) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(
|
|
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
|
|
&ctx,
|
|
) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(
|
|
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
|
|
&ctx,
|
|
) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(
|
|
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
|
|
&ctx,
|
|
) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(
|
|
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
|
|
&ctx,
|
|
) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(
|
|
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
|
|
&ctx,
|
|
) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
|
|
Ok(None) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
|
|
Ok(Some(Err(Error::Other("content error")))) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
}
|
|
}
|