use super::{Document, NodeError, NodeId};
#[derive(Debug)]
pub struct Builder<'a> {
doc: &'a mut Document,
id: NodeId,
error: Option<NodeError>,
}
impl Builder<'_> {
fn step(&mut self, outcome: Result<(), NodeError>) -> &mut Self {
if self.error.is_none() {
self.error = outcome.err();
}
self
}
pub fn attr(&mut self, local: &str, value: &str) -> &mut Self {
if self.error.is_some() {
return self;
}
let outcome = self.doc.set_attribute(self.id, None, local, value);
self.step(outcome)
}
pub fn attr_ns(
&mut self,
namespace: &str,
local: &str,
value: &str,
) -> &mut Self {
if self.error.is_some() {
return self;
}
let outcome =
self.doc
.set_attribute(self.id, Some(namespace), local, value);
self.step(outcome)
}
pub fn text(&mut self, text: &str) -> &mut Self {
if self.error.is_some() {
return self;
}
let outcome = self.doc.append_text(self.id, text).map(|_| ());
self.step(outcome)
}
pub fn child(
&mut self,
local: &str,
f: impl FnOnce(&mut Builder<'_>),
) -> &mut Self {
self.child_in(None, local, f)
}
pub fn child_ns(
&mut self,
namespace: &str,
local: &str,
f: impl FnOnce(&mut Builder<'_>),
) -> &mut Self {
self.child_in(Some(namespace), local, f)
}
fn child_in(
&mut self,
namespace: Option<&str>,
local: &str,
f: impl FnOnce(&mut Builder<'_>),
) -> &mut Self {
if self.error.is_some() {
return self;
}
match self.doc.append_element(self.id, namespace, local) {
Ok(child) => {
let mut inner = Builder {
doc: &mut *self.doc,
id: child,
error: None,
};
f(&mut inner);
self.error = inner.error;
}
Err(e) => self.error = Some(e),
}
self
}
pub fn finish(&mut self) -> Result<NodeId, NodeError> {
match self.error {
Some(e) => Err(e),
None => Ok(self.id),
}
}
}
impl Document {
pub fn build<'a>(&'a mut self, parent: NodeId, local: &str) -> Builder<'a> {
match self.append_element(parent, None, local) {
Ok(id) => Builder {
doc: self,
id,
error: None,
},
Err(e) => Builder {
doc: self,
id: parent,
error: Some(e),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_nested_subtree_is_built_in_one_expression() {
let mut doc = Document::empty();
let root = doc.root();
let cat = doc
.build(root, "catalogue")
.attr("version", "1.0")
.child("book", |b| {
let _ = b.attr("year", "1965").child("title", |t| {
let _ = t.text("Dune");
});
})
.child("book", |b| {
let _ = b.attr("year", "1961").child("title", |t| {
let _ = t.text("Solaris");
});
})
.finish()
.expect("root is live");
assert_eq!(doc.children(cat).len(), 2);
assert_eq!(doc.attribute(cat, "version"), Some("1.0"));
assert_eq!(doc.text(root), "DuneSolaris");
}
#[test]
fn the_builder_produces_the_same_tree_as_the_primitives() {
let mut built = Document::empty();
let root = built.root();
let _ = built
.build(root, "a")
.attr("k", "v")
.child("b", |b| {
let _ = b.text("x");
})
.child_ns("urn:n", "c", |c| {
let _ = c.attr_ns("urn:n", "k", "v");
})
.finish()
.expect("live");
let mut manual = Document::empty();
let mroot = manual.root();
let a = manual.append_element(mroot, None, "a").expect("live");
manual.set_attribute(a, None, "k", "v").expect("live");
let b = manual.append_element(a, None, "b").expect("live");
let _ = manual.append_text(b, "x").expect("live");
let c = manual.append_element(a, Some("urn:n"), "c").expect("live");
manual
.set_attribute(c, Some("urn:n"), "k", "v")
.expect("live");
assert_eq!(built.to_xml(), manual.to_xml());
}
#[test]
fn the_first_error_is_reported_and_later_steps_do_nothing() {
let mut doc = Document::empty();
let root = doc.root();
let _first = doc.append_element(root, None, "one").expect("live");
let before = doc.len();
let result = doc
.build(root, "two")
.attr("k", "v")
.child("c", |b| {
let _ = b.text("x");
})
.finish();
assert_eq!(result, Err(NodeError::RootElementExists));
assert_eq!(doc.len(), before, "nothing was added after the failure");
}
#[test]
fn a_failure_inside_a_closure_fails_the_whole_chain() {
let mut doc = Document::empty();
let root = doc.root();
let before = doc.len();
let result = doc
.build(root, "a")
.child("b", |b| {
let _ = b.child("not a name", |_| {}).text("after");
})
.finish();
assert_eq!(result, Err(NodeError::InvalidName));
assert_eq!(doc.len(), before + 2, "a and b were built, nothing after");
}
#[test]
fn building_on_a_stale_parent_reports_it_rather_than_panicking() {
let mut doc = Document::empty();
let root = doc.root();
let a = doc.append_element(root, None, "a").expect("live");
let b = doc.append_element(a, None, "b").expect("live");
doc.remove(a).expect("live");
let result = doc.build(b, "c").attr("k", "v").finish();
assert_eq!(result, Err(NodeError::Stale));
}
#[test]
fn building_under_a_text_node_is_refused() {
let mut doc = Document::empty();
let root = doc.root();
let a = doc.append_element(root, None, "a").expect("live");
let t = doc.append_text(a, "t").expect("live");
assert_eq!(doc.build(t, "c").finish(), Err(NodeError::NotAContainer));
assert!(doc.children(t).is_empty());
}
#[test]
fn a_built_document_round_trips() {
let mut doc = Document::empty();
let root = doc.root();
let _ = doc
.build(root, "r")
.attr("note", "a & b < c")
.child("child", |c| {
let _ = c.text("text & more");
})
.finish()
.expect("live");
let xml = doc.to_xml();
let reparsed = crate::parse(&xml).expect("must parse");
assert_eq!(reparsed.to_xml(), xml);
assert_eq!(reparsed.text(reparsed.root()), "text & more");
}
}