use std::convert::Infallible;
use std::error::Error;
use std::fmt::Debug;
use xml_stinks::deserializer::buffered::Buffered as BufferedDeserializer;
use xml_stinks::deserializer::{Deserializer, Error as DeserializerError, IgnoreEnd};
use xml_stinks::tagged::TagStart;
use xml_stinks::DeserializeTagged;
fn main() -> Result<(), Box<dyn Error>>
{
let mut deserializer = BufferedDeserializer::new(
concat!("<food><name>Banana</name><kind>Fruit</kind></food>",).as_bytes(),
);
let food = deserializer.de_tag::<Food>("food", IgnoreEnd::No)?;
println!("Food {} with kind {:?}", food.name, food.kind);
Ok(())
}
#[derive(Debug)]
struct Food
{
name: String,
kind: FoodKind,
}
impl DeserializeTagged for Food
{
type Error = FoodError;
fn deserialize<TDeserializer: Deserializer>(
_start: &TagStart,
deserializer: &mut TDeserializer,
) -> Result<Self, Self::Error>
{
let name =
deserializer.de_tag_with("name", IgnoreEnd::No, |_, deserializer| {
deserializer.de_text()
})?;
let kind =
deserializer.de_tag_with("kind", IgnoreEnd::No, |_, deserializer| {
match deserializer.de_text()?.as_str() {
"Fruit" => Ok(FoodKind::Fruit),
"Vegetable" => Ok(FoodKind::Vegetable),
unknown_kind => {
Err(FoodError::UnknownFoodKind(unknown_kind.to_string()))
}
}
})?;
Ok(Self { name, kind })
}
}
#[derive(Debug)]
enum FoodKind
{
Fruit,
Vegetable,
}
#[derive(Debug, thiserror::Error)]
enum FoodError
{
#[error("Unknown food kind '{0}'")]
UnknownFoodKind(String),
#[error(transparent)]
DeserializeFailed(#[from] DeserializerError<Infallible>),
}
impl<DeError: Into<Self>> From<DeserializerError<DeError>> for FoodError
{
fn from(err: DeserializerError<DeError>) -> Self
{
if let DeserializerError::DeserializeFailed(de_err) = err {
return de_err.into();
}
err.into_never_de_err().into()
}
}
#[cfg(test)]
mod tests
{
use mockall::mock;
use mockall::predicate::{always, eq};
use xml_stinks::deserializer::{Error, MaybeStatic};
use super::*;
mock! {
pub Deserializer {}
impl Deserializer for Deserializer
{
fn de_tag<De: DeserializeTagged>(
&mut self,
tag_name: &str,
ignore_end: IgnoreEnd,
) -> Result<De, Error<De::Error>>;
fn de_tag_with<TOutput, Err, Func>(
&mut self,
tag_name: &str,
ignore_end: IgnoreEnd,
deserialize: Func,
) -> Result<TOutput, Error<Err>>
where
TOutput: MaybeStatic,
Err: std::error::Error + Send + Sync + 'static,
Func: FnOnce(&TagStart, &mut MockDeserializer) -> Result<TOutput, Err> + MaybeStatic;
fn de_tag_list<De, TagName>(
&mut self,
tag_name: Option<TagName>
) -> Result<Vec<De>, Error<De::Error>>
where
De: DeserializeTagged,
TagName: AsRef<str> + MaybeStatic;
fn de_text(&mut self) -> Result<String, Error<Infallible>>;
fn skip_to_tag_start(&mut self, tag_name: &str) -> Result<(), Error<Infallible>>;
fn skip_to_tag_end(&mut self, tag_name: &str) -> Result<(), Error<Infallible>>;
}
}
#[test]
fn deserialize_food_works()
{
let mut mock_deserializer = MockDeserializer::new();
mock_deserializer
.expect_de_tag_with::<String, DeserializerError<Infallible>>()
.with(eq("name"), eq(IgnoreEnd::No), always())
.returning(|tag_name, _, func| {
let mut deserializer = MockDeserializer::new();
deserializer
.expect_de_text()
.returning(|| Ok("Carrot".to_string()))
.once();
Ok(func(&TagStart::new(tag_name), &mut deserializer)?)
})
.once();
mock_deserializer
.expect_de_tag_with::<FoodKind, FoodError>()
.with(eq("kind"), eq(IgnoreEnd::No), always())
.returning(|tag_name, _, func| {
let mut deserializer = MockDeserializer::new();
deserializer
.expect_de_text()
.returning(|| Ok("Vegetable".to_string()))
.once();
Ok(func(&TagStart::new(tag_name), &mut deserializer)?)
})
.once();
let food = Food::deserialize(&TagStart::new("food"), &mut mock_deserializer)
.expect("Expected Ok");
assert_eq!(food.name, "Carrot");
assert!(matches!(food.kind, FoodKind::Vegetable));
}
}