use crate::{Error, Result};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;
#[cfg(feature = "write")]
use xsd_parser_types::quick_xml::{BytesDecl, SerializeSync, Writer};
use xsd_parser_types::quick_xml::{
DeserializeSync, Error as XmlError, Event, IoReader, XmlReader, XmlReaderSync,
};
pub(crate) struct RootRecordingReader<R> {
inner: R,
root: Option<Vec<u8>>,
}
impl<R> RootRecordingReader<R> {
pub(crate) fn new(inner: R) -> Self {
Self { inner, root: None }
}
pub(crate) fn root_name(&self) -> Option<&[u8]> {
self.root.as_deref()
}
}
impl<R: XmlReader> XmlReader for RootRecordingReader<R> {
fn extend_error(&self, error: XmlError) -> XmlError {
self.inner.extend_error(error)
}
}
impl<'a, R: XmlReaderSync<'a>> XmlReaderSync<'a> for RootRecordingReader<R> {
fn read_event(&mut self) -> Result<Event<'a>, XmlError> {
let event = self.inner.read_event()?;
if self.root.is_none() {
if let Event::Start(start) | Event::Empty(start) = &event {
self.root = Some(start.local_name().as_ref().to_vec());
}
}
Ok(event)
}
}
fn ensure_root_is<R>(
reader: &RootRecordingReader<R>,
expected: &'static str,
what: &'static str,
) -> Result<()> {
match reader.root_name() {
Some(name) if name == expected.as_bytes() => Ok(()),
Some(name) => Err(Error::WrongRoot {
what,
expected,
found: String::from_utf8_lossy(name).into_owned(),
}),
None => Err(Error::MissingRoot { what }),
}
}
pub(crate) fn read_document<S, T, R>(source: R, root: &'static str, what: &'static str) -> Result<T>
where
R: BufRead,
S: DeserializeSync<'static, RootRecordingReader<IoReader<R>>>,
S::Error: std::error::Error + Send + Sync + 'static,
T: TryFrom<S, Error = Error>,
{
let mut reader = RootRecordingReader::new(IoReader::new(source));
let raw = S::deserialize(&mut reader).map_err(|error| Error::Parse {
what,
path: None,
source: Box::new(error),
})?;
ensure_root_is(&reader, root, what)?;
T::try_from(raw)
}
pub(crate) fn read_document_at<S, T>(
path: &Path,
root: &'static str,
what: &'static str,
) -> Result<T>
where
S: DeserializeSync<'static, RootRecordingReader<IoReader<BufReader<File>>>>,
S::Error: std::error::Error + Send + Sync + 'static,
T: TryFrom<S, Error = Error>,
{
let file = File::open(path).map_err(|source| Error::Open {
path: path.to_path_buf(),
source,
})?;
read_document::<S, T, _>(BufReader::new(file), root, what)
.map_err(|error| error.with_path(path))
}
#[cfg(feature = "write")]
pub(crate) fn write_document<'v, S, T, W>(
value: &'v T,
root: &str,
what: &'static str,
sink: W,
) -> Result<()>
where
W: std::io::Write,
S: TryFrom<&'v T, Error = Error> + SerializeSync,
<S as SerializeSync>::Error: std::error::Error + Send + Sync + 'static,
{
let raw = S::try_from(value)?;
let failed_to_write = |source| Error::Write {
what,
path: None,
source,
};
let mut writer = Writer::new_with_indent(sink, b' ', 4);
writer
.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
.map_err(failed_to_write)?;
raw.serialize(root, &mut writer)
.map_err(|error| Error::Serialize {
what,
source: Box::new(error),
})?;
writer.get_mut().write_all(b"\n").map_err(failed_to_write)?;
Ok(())
}
#[cfg(feature = "write")]
pub(crate) fn write_document_at<'v, S, T>(
value: &'v T,
root: &str,
what: &'static str,
path: &Path,
) -> Result<()>
where
S: TryFrom<&'v T, Error = Error> + SerializeSync,
<S as SerializeSync>::Error: std::error::Error + Send + Sync + 'static,
{
let file = File::create(path).map_err(|source| Error::Create {
path: path.to_path_buf(),
source,
})?;
write_document::<S, T, _>(value, root, what, std::io::BufWriter::new(file))
.map_err(|error| error.with_path(path))
}