From bdce3a36f72de6097fb78a917e29b7e093a6ef58 Mon Sep 17 00:00:00 2001 From: HampusM Date: Thu, 11 May 2023 21:27:36 +0200 Subject: docs: add examples --- Cargo.toml | 7 +++ examples/data.xml | 8 +++ examples/simple.rs | 143 ++++++++++++++++++++++++++++++++++++++++++ examples/testing.rs | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 334 insertions(+) create mode 100644 examples/data.xml create mode 100644 examples/simple.rs create mode 100644 examples/testing.rs diff --git a/Cargo.toml b/Cargo.toml index 516e5d2..d3497c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,10 @@ deserializer-static-generics = [] quick-xml = "0.27.1" thiserror = "1.0.38" +[dev-dependencies] +mockall = "0.11.4" + +[[example]] +name = "testing" +test = true +required-features = ["deserializer-static-generics"] diff --git a/examples/data.xml b/examples/data.xml new file mode 100644 index 0000000..acda261 --- /dev/null +++ b/examples/data.xml @@ -0,0 +1,8 @@ + + + Hello Foo there + + Icecream + Magenta + + diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 0000000..a4b76f2 --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,143 @@ +use std::convert::Infallible; +use std::error::Error; +use std::fmt::Debug; +use std::fs::File; +use std::io::BufReader; +use std::path::Path; + +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; + +const MANIFEST_DIR: &str = std::env!("CARGO_MANIFEST_DIR"); + +fn main() -> Result<(), Box> +{ + let data_file = Path::new(MANIFEST_DIR).join("examples/data.xml"); + + let reader = BufReader::new(File::open(data_file)?); + + let mut deserializer = BufferedDeserializer::new(reader); + + // Skip the + deserializer.skip_to_tag_start("data")?; + + let data = deserializer.de_tag::("data", IgnoreEnd::No)?; + + println!("The following data was deserialized:\n"); + + println!("Greeting: {}", data.greeting); + println!("Something: {}", data.something); + println!("Favorite food: {}", data.favorites.food); + println!("Favorite color: {}", data.favorites.color); + + Ok(()) +} + +#[derive(Debug)] +struct Data +{ + greeting: String, + something: String, + favorites: Favorites, +} + +impl DeserializeTagged for Data +{ + type Error = DataError; + + fn deserialize( + _start: &TagStart, + deserializer: &mut TDeserializer, + ) -> Result + { + let greeting_a = deserializer.de_text()?; + + let something = + deserializer.de_tag_with("something", IgnoreEnd::No, |_, deserializer| { + deserializer.de_text() + })?; + + let greeting_b = deserializer.de_text()?; + + let favorites = deserializer.de_tag::("favorites", IgnoreEnd::No)?; + + Ok(Self { + greeting: format!("{greeting_a} {greeting_b}"), + something, + favorites, + }) + } +} + +#[derive(Debug, thiserror::Error)] +enum DataError +{ + #[error("Invalid favorites")] + InvalidFavorites(#[from] FavoritesError), + + #[error(transparent)] + DeserializeFailed(#[from] DeserializerError), +} + +impl> From> for DataError +{ + fn from(err: DeserializerError) -> Self + { + if let DeserializerError::DeserializeFailed(de_err) = err { + return de_err.into(); + } + + err.into_never_de_err().into() + } +} + +#[derive(Debug)] +struct Favorites +{ + food: String, + color: String, +} + +impl DeserializeTagged for Favorites +{ + type Error = FavoritesError; + + fn deserialize( + _start: &TagStart, + deserializer: &mut TDeserializer, + ) -> Result + { + let food = + deserializer.de_tag_with("food", IgnoreEnd::No, |_, deserializer| { + deserializer.de_text() + })?; + + let color = + deserializer.de_tag_with("color", IgnoreEnd::No, |_, deserializer| { + deserializer.de_text() + })?; + + Ok(Self { food, color }) + } +} + +#[derive(Debug, thiserror::Error)] +enum FavoritesError +{ + #[error(transparent)] + DeserializeFailed(#[from] DeserializerError), +} + +impl> From> for FavoritesError +{ + fn from(err: DeserializerError) -> Self + { + if let DeserializerError::DeserializeFailed(de_err) = err { + return de_err.into(); + } + + err.into_never_de_err().into() + } +} diff --git a/examples/testing.rs b/examples/testing.rs new file mode 100644 index 0000000..3a9d912 --- /dev/null +++ b/examples/testing.rs @@ -0,0 +1,176 @@ +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> +{ + let mut deserializer = BufferedDeserializer::new( + concat!("BananaFruit",).as_bytes(), + ); + + let food = deserializer.de_tag::("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( + _start: &TagStart, + deserializer: &mut TDeserializer, + ) -> Result + { + 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), +} + +impl> From> for FoodError +{ + fn from(err: DeserializerError) -> 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( + &mut self, + tag_name: &str, + ignore_end: IgnoreEnd, + ) -> Result>; + + fn de_tag_with( + &mut self, + tag_name: &str, + ignore_end: IgnoreEnd, + deserialize: Func, + ) -> Result> + where + TOutput: MaybeStatic, + Err: std::error::Error + Send + Sync + 'static, + Func: FnOnce(&TagStart, &mut MockDeserializer) -> Result + MaybeStatic; + + fn de_tag_list( + &mut self, + tag_name: Option + ) -> Result, Error> + where + De: DeserializeTagged, + TagName: AsRef + MaybeStatic; + + fn de_text(&mut self) -> Result>; + + fn skip_to_tag_start(&mut self, tag_name: &str) -> Result<(), Error>; + + fn skip_to_tag_end(&mut self, tag_name: &str) -> Result<(), Error>; + } + } + + #[test] + fn deserialize_food_works() + { + let mut mock_deserializer = MockDeserializer::new(); + + mock_deserializer + .expect_de_tag_with::>() + .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::() + .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)); + } +} -- cgit v1.2.3-18-g5258