From ad142e9e749adf64642168c0d221b0cf47f149c7 Mon Sep 17 00:00:00 2001 From: HampusM Date: Sun, 14 May 2023 19:51:17 +0200 Subject: refactor: use xml-stinks --- Cargo.toml | 5 +- src/api_interface_definition.rs | 398 +++++++++--------- src/command.rs | 629 +++++++++++++---------------- src/deserialization/buffer_deserializer.rs | 240 ----------- src/deserialization/mod.rs | 127 ------ src/lib.rs | 62 ++- src/test_utils.rs | 44 ++ src/util.rs | 15 + 8 files changed, 583 insertions(+), 937 deletions(-) delete mode 100644 src/deserialization/buffer_deserializer.rs delete mode 100644 src/deserialization/mod.rs create mode 100644 src/test_utils.rs create mode 100644 src/util.rs diff --git a/Cargo.toml b/Cargo.toml index 3af2b0f..d2ea91f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,13 +24,14 @@ all-features = true rustdoc-args = ["--cfg", "doc_cfg"] [dependencies] -quick-xml = { version = "0.27.1" } thiserror = "1.0.38" +xml-stinks = { version = "0.1.0" } [dev-dependencies] pretty_assertions = "1.3.0" criterion = "0.4.0" -ridicule = "0.3.0" +mockall = "0.11.4" +xml-stinks = { version = "0.1.0", features = ["deserializer-static-generics"] } [[bench]] name = "registry" diff --git a/src/api_interface_definition.rs b/src/api_interface_definition.rs index 784b7d0..8208dbe 100644 --- a/src/api_interface_definition.rs +++ b/src/api_interface_definition.rs @@ -1,8 +1,12 @@ //! GL API interface definition. -use quick_xml::events::BytesStart; +use std::borrow::Cow; -use crate::deserialization::{Deserialize, Deserializer, DeserializerError}; +use xml_stinks::deserializer::Deserializer; +use xml_stinks::tagged::{TagStart, TagStartError}; +use xml_stinks::{impl_from_deserializer_error, DeserializeTagged}; + +use crate::util::impl_from_deserializer_err_wrapped; use crate::DeserializationError; /// GL API interface definition. @@ -73,48 +77,39 @@ impl APIInterfaceDefinition } } -impl Deserialize for APIInterfaceDefinition +impl DeserializeTagged for APIInterfaceDefinition { type Error = Error; fn deserialize( - start: &BytesStart, + start: &TagStart, deserializer: &mut TDeserializer, ) -> Result { - let api_name = String::from_utf8( - start - .try_get_attribute(b"api") - .map_err(DeserializerError::ReadFailed)? - .ok_or_else(|| Error::MissingAPIName)? - .value - .into_owned(), - ) - .map_err(|_| Error::APINameNotUTF8)?; - - let name = String::from_utf8( - start - .try_get_attribute(b"name") - .map_err(DeserializerError::ReadFailed)? - .ok_or_else(|| Error::MissingName)? - .value - .into_owned(), - ) - .map_err(|_| Error::NameNotUTF8)?; - - let version = String::from_utf8( - start - .try_get_attribute(b"number") - .map_err(DeserializerError::ReadFailed)? - .ok_or_else(|| Error::MissingVersionNumber)? - .value - .into_owned(), - ) - .map_err(|_| Error::VersionNotUTF8)?; - - let requirements = deserializer.de_tag_list::("require")?; - - let removals = deserializer.de_tag_list::("remove")?; + let api_name = start + .get_attribute("api")? + .ok_or_else(|| Error::MissingAPIName)? + .value() + .map_err(|_| Error::APINameNotUTF8)? + .into_owned(); + + let name = start + .get_attribute("name")? + .ok_or_else(|| Error::MissingName)? + .value() + .map_err(|_| Error::NameNotUTF8)? + .into_owned(); + + let version = start + .get_attribute("number")? + .ok_or_else(|| Error::MissingVersionNumber)? + .value() + .map_err(|_| Error::VersionNotUTF8)? + .into_owned(); + + let requirements = deserializer.de_tag_list::(Some("require"))?; + + let removals = deserializer.de_tag_list::(Some("remove"))?; Ok(Self { api_name, @@ -154,19 +149,26 @@ pub enum Error #[error("Version is not valid UTF-8")] VersionNotUTF8, + /// Invalid requirement. + #[error("Invalid requirement")] + InvalidRequirement(#[from] RequirementError), + + /// Invalid removal. + #[error("Invalid removal")] + InvalidRemoval(#[from] RemovalError), + /// Deserialization failed. #[error("Deserialization failed")] DeserializationFailed(#[from] DeserializationError), -} -impl From for Error -{ - fn from(err: DeserializerError) -> Self - { - DeserializationError(err).into() - } + /// Invalid tag start. + #[error("Invalid tag start")] + InvalidTagStart(#[from] TagStartError), } +impl_from_deserializer_err_wrapped!(Error); +impl_from_deserializer_error!(Error); + /// GL API feature requirement. #[derive(Debug, PartialEq, Eq)] pub struct Requirement @@ -215,38 +217,36 @@ impl Requirement } } -impl Deserialize for Requirement +impl DeserializeTagged for Requirement { type Error = RequirementError; fn deserialize( - start: &BytesStart, + start: &TagStart, deserializer: &mut TDeserializer, ) -> Result { - let profile = match start - .try_get_attribute(b"profile") - .map_err(DeserializerError::ReadFailed)? - { + let profile = match start.get_attribute("profile")? { Some(comment_attr) => Some( - String::from_utf8(comment_attr.value.into_owned()) - .map_err(|_| RequirementError::ProfileNotUTF8)?, + comment_attr + .value() + .map_err(|_| RequirementError::ProfileNotUTF8)? + .into_owned(), ), None => None, }; - let comment = match start - .try_get_attribute(b"comment") - .map_err(DeserializerError::ReadFailed)? - { + let comment = match start.get_attribute("comment")? { Some(comment_attr) => Some( - String::from_utf8(comment_attr.value.into_owned()) - .map_err(|_| RequirementError::CommentNotUTF8)?, + comment_attr + .value() + .map_err(|_| RequirementError::CommentNotUTF8)? + .into_owned(), ), None => None, }; - let features = deserializer.de_list::()?; + let features = deserializer.de_tag_list::(None::<&str>)?; Ok(Self { profile, @@ -268,19 +268,22 @@ pub enum RequirementError #[error("Comment is not valid UTF-8")] CommentNotUTF8, + /// Invalid feature. + #[error("Invalid feature")] + InvalidFeature(#[from] FeatureError), + /// Deserialization failed. #[error("Deserialization failed")] DeserializationFailed(#[from] DeserializationError), -} -impl From for RequirementError -{ - fn from(err: DeserializerError) -> Self - { - DeserializationError(err).into() - } + /// Invalid tag start. + #[error("Invalid tag start")] + InvalidTagStart(#[from] TagStartError), } +impl_from_deserializer_err_wrapped!(RequirementError); +impl_from_deserializer_error!(RequirementError); + /// GL API feature removal. #[derive(Debug, PartialEq, Eq)] pub struct Removal @@ -329,36 +332,30 @@ impl Removal } } -impl Deserialize for Removal +impl DeserializeTagged for Removal { type Error = RemovalError; fn deserialize( - start: &BytesStart, + start: &TagStart, deserializer: &mut TDeserializer, ) -> Result { - let profile = String::from_utf8( - start - .try_get_attribute(b"profile") - .map_err(DeserializerError::ReadFailed)? - .ok_or_else(|| RemovalError::MissingProfile)? - .value - .into_owned(), - ) - .map_err(|_| RemovalError::ProfileNotUTF8)?; - - let comment = String::from_utf8( - start - .try_get_attribute(b"comment") - .map_err(DeserializerError::ReadFailed)? - .ok_or_else(|| RemovalError::MissingComment)? - .value - .into_owned(), - ) - .map_err(|_| RemovalError::CommentNotUTF8)?; - - let features = deserializer.de_list::()?; + let profile = start + .get_attribute("profile")? + .ok_or_else(|| RemovalError::MissingProfile)? + .value() + .map_err(|_| RemovalError::ProfileNotUTF8)? + .into_owned(); + + let comment = start + .get_attribute("comment")? + .ok_or_else(|| RemovalError::MissingComment)? + .value() + .map_err(|_| RemovalError::CommentNotUTF8)? + .into_owned(); + + let features = deserializer.de_tag_list::(None::<&str>)?; Ok(Self { profile, @@ -388,19 +385,22 @@ pub enum RemovalError #[error("Comment is not valid UTF-8")] CommentNotUTF8, + /// Invalid feature. + #[error("Invalid feature")] + InvalidFeature(#[from] FeatureError), + /// Deserialization failed. #[error("Deserialization failed")] DeserializationFailed(#[from] DeserializationError), -} -impl From for RemovalError -{ - fn from(err: DeserializerError) -> Self - { - DeserializationError(err).into() - } + /// Invalid tag start. + #[error("Invalid tag start")] + InvalidTagStart(#[from] TagStartError), } +impl_from_deserializer_err_wrapped!(RemovalError); +impl_from_deserializer_error!(RemovalError); + /// GL API feature. #[derive(Debug, PartialEq, Eq)] pub struct Feature @@ -449,16 +449,16 @@ impl Feature } } -impl Deserialize for Feature +impl DeserializeTagged for Feature { type Error = FeatureError; fn deserialize( - start: &BytesStart, + start: &TagStart, _deserializer: &mut TDeserializer, ) -> Result { - let kind = match start.name().as_ref() { + let kind = match start.name_bytes() { b"enum" => Ok(FeatureKind::Enum), b"command" => Ok(FeatureKind::Command), b"type" => Ok(FeatureKind::Type), @@ -467,26 +467,22 @@ impl Deserialize for Feature )), }?; - let name = String::from_utf8( - start - .try_get_attribute(b"name") - .map_err(DeserializerError::ReadFailed)? - .ok_or_else(|| FeatureError::MissingName)? - .value - .into_owned(), - ) - .map_err(|_| FeatureError::NameNotUTF8)?; - - let comment = match start - .try_get_attribute(b"comment") - .map_err(DeserializerError::ReadFailed)? - { - Some(comment_attr) => Some( - String::from_utf8(comment_attr.value.into_owned()) - .map_err(|_| FeatureError::CommentNotUTF8)?, - ), - None => None, - }; + let name = start + .get_attribute("name")? + .ok_or_else(|| FeatureError::MissingName)? + .value() + .map_err(|_| FeatureError::NameNotUTF8)? + .into_owned(); + + let comment = start + .get_attribute("comment")? + .map(|comment_attr| { + comment_attr + .value() + .map(Cow::into_owned) + .map_err(|_| FeatureError::CommentNotUTF8) + }) + .transpose()?; Ok(Self { kind, @@ -519,16 +515,15 @@ pub enum FeatureError /// Deserialization failed. #[error("Deserialization failed")] DeserializationFailed(#[from] DeserializationError), -} -impl From for FeatureError -{ - fn from(err: DeserializerError) -> Self - { - DeserializationError(err).into() - } + /// Invalid tag start. + #[error("Invalid tag start")] + InvalidTagStart(#[from] TagStartError), } +impl_from_deserializer_err_wrapped!(FeatureError); +impl_from_deserializer_error!(FeatureError); + /// GL API feature kind. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FeatureKind @@ -547,9 +542,10 @@ pub enum FeatureKind mod tests { use pretty_assertions::{assert_eq, assert_str_eq}; + use xml_stinks::attribute::Attribute; use super::*; - use crate::deserialization::MockDeserializer; + use crate::test_utils::MockDeserializer; #[test] fn deserialize_feature_works() @@ -557,7 +553,8 @@ mod tests let mut mock_deserializer = MockDeserializer::new(); let commentless_enum_feature = Feature::deserialize( - &BytesStart::new("enum").with_attributes([("name", "GL_BLEND")]), + &TagStart::new("enum") + .with_attributes([Attribute::new("name", "GL_BLEND".as_bytes())]), &mut mock_deserializer, ) .expect("Expected Ok"); @@ -567,9 +564,12 @@ mod tests assert!(commentless_enum_feature.comment.is_none()); let enum_feature = Feature::deserialize( - &BytesStart::new("enum").with_attributes([ - ("name", "GL_VERTEX_BINDING_BUFFER"), - ("comment", "Added in 2013/10/22 update to the spec"), + &TagStart::new("enum").with_attributes([ + Attribute::new("name", "GL_VERTEX_BINDING_BUFFER".as_bytes()), + Attribute::new( + "comment", + "Added in 2013/10/22 update to the spec".as_bytes(), + ), ]), &mut mock_deserializer, ) @@ -584,7 +584,8 @@ mod tests ); let command_feature = Feature::deserialize( - &BytesStart::new("command").with_attributes([("name", "glCreateBuffers")]), + &TagStart::new("command") + .with_attributes([Attribute::new("name", "glCreateBuffers".as_bytes())]), &mut mock_deserializer, ) .expect("Expected Ok"); @@ -594,7 +595,8 @@ mod tests assert!(command_feature.comment.is_none()); let type_feature = Feature::deserialize( - &BytesStart::new("type").with_attributes([("name", "GLbyte")]), + &TagStart::new("type") + .with_attributes([Attribute::new("name", "GLbyte".as_bytes())]), &mut mock_deserializer, ) .expect("Expected Ok"); @@ -604,7 +606,7 @@ mod tests assert!(type_feature.comment.is_none()); assert!(matches!( - Feature::deserialize(&BytesStart::new("foo"), &mut mock_deserializer), + Feature::deserialize(&TagStart::new("foo"), &mut mock_deserializer), Err(FeatureError::UnknownFeatureKind(_)) )); } @@ -614,22 +616,22 @@ mod tests { let mut mock_deserializer = MockDeserializer::new(); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer.expect_de_list().times(1).returning(|_| { + mock_deserializer + .expect_de_tag_list::<_, &str>() + .times(1) + .returning(|_| { Ok(vec![ Feature::new(FeatureKind::Command, "glNewList", None), Feature::new(FeatureKind::Command, "glEndList", None), ]) }); - } let removal = Removal::deserialize( - &BytesStart::new("remove").with_attributes([ - ("profile", "core"), - ( + &TagStart::new("remove").with_attributes([ + Attribute::new("profile", "core".as_bytes()), + Attribute::new( "comment", - "Compatibility-only GL 1.0 features removed from GL 3.2", + "Compatibility-only GL 1.0 features removed from GL 3.2".as_bytes(), ), ]), &mut mock_deserializer, @@ -651,39 +653,39 @@ mod tests { let mut mock_deserializer = MockDeserializer::new(); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer.expect_de_list().times(1).returning(|_| { + mock_deserializer + .expect_de_tag_list::<_, &str>() + .times(1) + .returning(|_| { Ok(vec![ Feature::new(FeatureKind::Command, "glNewList", None), Feature::new(FeatureKind::Command, "glEndList", None), ]) }); - } - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer.expect_de_list().times(1).returning(|_| { + mock_deserializer + .expect_de_tag_list::<_, &str>() + .times(1) + .returning(|_| { Ok(vec![ Feature::new(FeatureKind::Enum, "GL_COLOR_TABLE", None), Feature::new(FeatureKind::Enum, "GL_MINMAX", None), Feature::new(FeatureKind::Enum, "GL_HISTOGRAM", None), ]) }); - } - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer.expect_de_list().times(1).returning(|_| { + mock_deserializer + .expect_de_tag_list::<_, &str>() + .times(1) + .returning(|_| { Ok(vec![ Feature::new(FeatureKind::Enum, "GL_STACK_UNDERFLOW", None), Feature::new(FeatureKind::Enum, "GL_STACK_OVERFLOW", None), ]) }); - } let requirement = - Requirement::deserialize(&BytesStart::new("require"), &mut mock_deserializer) + Requirement::deserialize(&TagStart::new("require"), &mut mock_deserializer) .expect("Expected Ok"); assert!(requirement.profile.is_none()); @@ -692,7 +694,8 @@ mod tests assert_eq!(requirement.features.len(), 2); let requirement = Requirement::deserialize( - &BytesStart::new("require").with_attributes([("profile", "compatibility")]), + &TagStart::new("require") + .with_attributes([Attribute::new("profile", "compatibility".as_bytes())]), &mut mock_deserializer, ) .expect("Expected Ok"); @@ -703,14 +706,15 @@ mod tests assert_eq!(requirement.features.len(), 3); let requirement = Requirement::deserialize( - &BytesStart::new("require").with_attributes([ - ("profile", "core"), - ( + &TagStart::new("require").with_attributes([ + Attribute::new("profile", "core".as_bytes()), + Attribute::new( "comment", concat!( "Restore functionality removed in GL 3.2 core ", "to GL 4.3. Needed for debug interface." - ), + ) + .as_bytes(), ), ]), &mut mock_deserializer, @@ -734,49 +738,43 @@ mod tests { let mut mock_deserializer = MockDeserializer::new(); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_tag_list() - .times(1) - .returning(|_, _| { - Ok(vec![ - Requirement::new( - None, - None, - vec![ - Feature::new(FeatureKind::Command, "glFenceSync", None), - Feature::new(FeatureKind::Enum, "GL_WAIT_FAILED", None), - ], - ), - Requirement::new( - None, - Some("Reuse ARB_sync".to_string()), - [Feature::new(FeatureKind::Enum, "GL_OBJECT_TYPE", None)], - ), - ]) - }); - } + mock_deserializer + .expect_de_tag_list::<_, &str>() + .times(1) + .returning(|_| { + Ok(vec![ + Requirement::new( + None, + None, + vec![ + Feature::new(FeatureKind::Command, "glFenceSync", None), + Feature::new(FeatureKind::Enum, "GL_WAIT_FAILED", None), + ], + ), + Requirement::new( + None, + Some("Reuse ARB_sync".to_string()), + [Feature::new(FeatureKind::Enum, "GL_OBJECT_TYPE", None)], + ), + ]) + }); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_tag_list() - .times(1) - .returning(|_, _| { - Ok(vec![Removal::new( - "core", - "Compatibility-only GL 1.0 features removed from GL 3.2", - vec![Feature::new(FeatureKind::Command, "glBegin", None)], - )]) - }); - } + mock_deserializer + .expect_de_tag_list::<_, &str>() + .times(1) + .returning(|_| { + Ok(vec![Removal::new( + "core", + "Compatibility-only GL 1.0 features removed from GL 3.2", + vec![Feature::new(FeatureKind::Command, "glBegin", None)], + )]) + }); let api_interface_definition = APIInterfaceDefinition::deserialize( - &BytesStart::new("feature").with_attributes([ - ("api", "gl"), - ("name", "GL_VERSION_3_2"), - ("number", "3.2"), + &TagStart::new("feature").with_attributes([ + Attribute::new("api", "gl".as_bytes()), + Attribute::new("name", "GL_VERSION_3_2".as_bytes()), + Attribute::new("number", "3.2".as_bytes()), ]), &mut mock_deserializer, ) diff --git a/src/command.rs b/src/command.rs index deab0eb..a0c9e23 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,14 +1,11 @@ //! OpenGL command. -use quick_xml::events::BytesStart; - -use crate::deserialization::{ - Deserialize, - DeserializeWithFn, - Deserializer, - DeserializerError, - IgnoreEnd, - ResultExt, -}; +use std::convert::Infallible; + +use xml_stinks::deserializer::{Deserializer, Error as DeserializerError, IgnoreEnd}; +use xml_stinks::tagged::{TagStart, TagStartError}; +use xml_stinks::{impl_from_deserializer_error, DeserializeTagged, ResultExt}; + +use crate::util::impl_from_deserializer_err_wrapped; use crate::DeserializationError; /// A command. @@ -48,21 +45,20 @@ impl Command } } -impl Deserialize for Command +impl DeserializeTagged for Command { type Error = Error; fn deserialize( - start: &BytesStart, + start: &TagStart, deserializer: &mut TDeserializer, ) -> Result { let prototype = deserializer.de_tag::("proto", IgnoreEnd::No)?; - let parameters = deserializer.de_tag_list::("param")?; + let parameters = deserializer.de_tag_list::(Some("param"))?; - deserializer - .skip_to_tag_end(std::str::from_utf8(start.name().as_ref()).unwrap())?; + deserializer.skip_to_tag_end(start.name()?)?; Ok(Self { prototype, @@ -90,16 +86,15 @@ pub enum Error /// Deserialization failed. #[error("Deserialization failed")] DeserializationFailed(#[from] DeserializationError), -} -impl From for Error -{ - fn from(err: DeserializerError) -> Self - { - DeserializationError(err).into() - } + /// Invalid tag start. + #[error("Invalid tag start")] + InvalidTagStart(#[from] TagStartError), } +impl_from_deserializer_err_wrapped!(Error); +impl_from_deserializer_error!(Error); + /// A command prototype. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Prototype @@ -134,22 +129,22 @@ impl Prototype } } -impl Deserialize for Prototype +impl DeserializeTagged for Prototype { type Error = PrototypeError; fn deserialize( - _start: &BytesStart, + _start: &TagStart, deserializer: &mut TDeserializer, ) -> Result { let return_type = deserialize_type::(deserializer)?; - let name = deserializer.de_tag_with::<_, _, DeserializeWithFn<_, _, _>>( - "name", - IgnoreEnd::No, - |_, deserializer| deserializer.de_text(), - )?; + // let name = deserializer.de_tag_with::<_, _, DeserializeWithFn<_, _, _>>( + let name = + deserializer.de_tag_with("name", IgnoreEnd::No, |_, deserializer| { + deserializer.de_text() + })?; Ok(Self { name, return_type }) } @@ -172,13 +167,8 @@ pub enum PrototypeError DeserializationFailed(#[from] DeserializationError), } -impl From for PrototypeError -{ - fn from(err: DeserializerError) -> Self - { - DeserializationError(err).into() - } -} +impl_from_deserializer_err_wrapped!(PrototypeError); +impl_from_deserializer_error!(PrototypeError); /// A command parameter. #[derive(Debug, Clone, PartialEq, Eq)] @@ -214,22 +204,22 @@ impl Parameter } } -impl Deserialize for Parameter +impl DeserializeTagged for Parameter { type Error = ParameterError; fn deserialize( - _start: &BytesStart, + _start: &TagStart, deserializer: &mut TDeserializer, ) -> Result { let ty = deserialize_type::(deserializer)?; - let name = deserializer.de_tag_with::<_, _, DeserializeWithFn<_, _, _>>( - "name", - IgnoreEnd::No, - |_, deserializer| deserializer.de_text(), - )?; + // let name = deserializer.de_tag_with::<_, _, DeserializeWithFn<_, _, _>>( + let name = + deserializer.de_tag_with("name", IgnoreEnd::No, |_, deserializer| { + deserializer.de_text() + })?; Ok(Self { name, ty }) } @@ -248,27 +238,22 @@ pub enum ParameterError DeserializationFailed(#[from] DeserializationError), } -impl From for ParameterError -{ - fn from(err: DeserializerError) -> Self - { - DeserializationError(err).into() - } -} +impl_from_deserializer_err_wrapped!(ParameterError); +impl_from_deserializer_error!(ParameterError); fn deserialize_type(deserializer: &mut impl Deserializer) -> Result where - Err: From, + Err: From>, { let type_before = deserializer.de_text().try_event()?; + //.de_tag_with::<_, _, DeserializeWithFn<_, _, _>>( let type_ptype = deserializer - .de_tag_with::<_, _, DeserializeWithFn<_, _, _>>( - "ptype", - IgnoreEnd::No, - |_, deserializer| deserializer.de_text(), - ) - .try_event()?; + .de_tag_with("ptype", IgnoreEnd::No, |_, deserializer| { + deserializer.de_text() + }) + .try_event() + .map_err(Into::into)?; let type_after = deserializer.de_text().try_event()?; @@ -308,92 +293,83 @@ where #[cfg(test)] mod tests { + use mockall::predicate::{always, eq, function}; use pretty_assertions::assert_str_eq; - use quick_xml::events::Event; - use ridicule::predicate::{always, eq, function}; use super::*; - use crate::deserialization::MockDeserializer; + use crate::test_utils::MockDeserializer; #[test] fn deserialize_prototype_works_with_ptype() { let mut mock_deserializer = MockDeserializer::new(); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| { - Err(DeserializerError::UnexpectedEvent { - expected_event_name: "text".to_string(), - found_event: Event::Start(BytesStart::new("ptype")), - }) + mock_deserializer + .expect_de_text() + .returning(|| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "text".to_string(), + found_event: "ptype".to_string(), }) - .times(1); - } - - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("GLuint".to_string())) - .times(1); - } + }) + .times(1); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_tag_with::>() + mock_deserializer + .expect_de_tag_with::>() .with( eq("ptype"), function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), always(), ) - .returning(|deserializer, tag_name, _, func| { - func(&BytesStart::new(tag_name), deserializer) + .returning(|tag_name, _, func| { + let mut inner_mock_deserializer = MockDeserializer::new(); + + inner_mock_deserializer + .expect_de_text() + .returning(|| Ok("GLuint".to_string())) + .times(1); + + Ok(func( + &TagStart::new(tag_name), + &mut inner_mock_deserializer, + )?) }) .times(1); - } - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| { - Err(DeserializerError::UnexpectedEvent { - expected_event_name: "text".to_string(), - found_event: Event::Start(BytesStart::new("name")), - }) + mock_deserializer + .expect_de_text() + .returning(|| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "text".to_string(), + found_event: "name".to_string(), }) - .times(1); - } - - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("glDoComplicatedThing".to_string())) - .times(1); - } + }) + .times(1); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_tag_with::>() - .with( - eq("name"), - function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), - always(), - ) - .returning(|deserializer, tag_name, _, func| { - func(&BytesStart::new(tag_name), deserializer) - }) - .times(1); - } + mock_deserializer + .expect_de_tag_with::>() + .with( + eq("name"), + function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), + always(), + ) + .returning(|tag_name, _, func| { + let mut inner_mock_deserializer = MockDeserializer::new(); + + inner_mock_deserializer + .expect_de_text() + .returning(|| Ok("glDoComplicatedThing".to_string())) + .times(1); + + Ok(func( + &TagStart::new(tag_name), + &mut inner_mock_deserializer, + )?) + }) + .times(1); let prototype = - Prototype::deserialize(&BytesStart::new("proto"), &mut mock_deserializer) + Prototype::deserialize(&TagStart::new("proto"), &mut mock_deserializer) .expect("Expected Ok"); assert_str_eq!(prototype.name, "glDoComplicatedThing"); @@ -405,70 +381,60 @@ mod tests { let mut mock_deserializer = MockDeserializer::new(); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("void".to_string())) - .times(1); - } + mock_deserializer + .expect_de_text() + .returning(|| Ok("void".to_string())) + .times(1); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_tag_with::>() + mock_deserializer + .expect_de_tag_with::>() .with( eq("ptype"), function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), always(), ) - .returning(|_, _, _, _| { + .returning(|_, _, _| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "start".to_string(), - found_event: Event::Start(BytesStart::new("name")), + found_event: "name".to_string(), }) }) .times(1); - } - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| { - Err(DeserializerError::UnexpectedEvent { - expected_event_name: "text".to_string(), - found_event: Event::Start(BytesStart::new("name")), - }) + mock_deserializer + .expect_de_text() + .returning(|| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "text".to_string(), + found_event: "name".to_string(), }) - .times(1); - } - - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("glDoSomeThing".to_string())) - .times(1); - } + }) + .times(1); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_tag_with::>() + mock_deserializer + .expect_de_tag_with::>() .with( eq("name"), function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), always(), ) - .returning(|deserializer, tag_name, _, func| { - func(&BytesStart::new(tag_name), deserializer) + .returning(|tag_name, _, func| { + let mut inner_mock_deserializer = MockDeserializer::new(); + + inner_mock_deserializer + .expect_de_text() + .returning(|| Ok("glDoSomeThing".to_string())) + .times(1); + + Ok(func( + &TagStart::new(tag_name), + &mut inner_mock_deserializer, + )?) }) .times(1); - } let prototype = - Prototype::deserialize(&BytesStart::new("proto"), &mut mock_deserializer) + Prototype::deserialize(&TagStart::new("proto"), &mut mock_deserializer) .expect("Expected Ok"); assert_str_eq!(prototype.name, "glDoSomeThing"); @@ -480,80 +446,72 @@ mod tests { let mut mock_deserializer = MockDeserializer::new(); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| { - Err(DeserializerError::UnexpectedEvent { - expected_event_name: "text".to_string(), - found_event: Event::Start(BytesStart::new("ptype")), - }) + mock_deserializer + .expect_de_text() + .returning(|| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "text".to_string(), + found_event: "ptype".to_string(), }) - .times(1); - } - - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("GLenum".to_string())) - .times(1); - } + }) + .times(1); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_tag_with::>() + mock_deserializer + .expect_de_tag_with::>() .with( eq("ptype"), function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), always(), ) - .returning(|deserializer, tag_name, _, func| { - func(&BytesStart::new(tag_name), deserializer) + .returning(|tag_name, _, func| { + let mut inner_mock_deserializer = MockDeserializer::new(); + + inner_mock_deserializer + .expect_de_text() + .returning(|| Ok("GLenum".to_string())) + .times(1); + + Ok(func( + &TagStart::new(tag_name), + &mut inner_mock_deserializer, + )?) }) .times(1); - } - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| { - Err(DeserializerError::UnexpectedEvent { - expected_event_name: "text".to_string(), - found_event: Event::Start(BytesStart::new("name")), - }) + mock_deserializer + .expect_de_text() + .returning(|| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "text".to_string(), + found_event: "name".to_string(), }) - .times(1); - } - - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("value".to_string())) - .times(1); - } + }) + .times(1); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_tag_with::>() + mock_deserializer + .expect_de_tag_with::>() .with( eq("name"), function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), always(), ) - .returning(|deserializer, tag_name, _, func| { - func(&BytesStart::new(tag_name), deserializer) + .returning(|tag_name, _, func| { + let mut inner_mock_deserializer = MockDeserializer::new(); + + inner_mock_deserializer + .expect_de_text() + .returning(|| Ok("value".to_string())) + .times(1); + + Ok(func( + &TagStart::new(tag_name), + &mut inner_mock_deserializer, + )?) }) .times(1); - } let parameter = - Parameter::deserialize(&BytesStart::new("param"), &mut mock_deserializer) + Parameter::deserialize(&TagStart::new("param"), &mut mock_deserializer) .expect("Expected Ok"); assert_str_eq!(parameter.name, "value"); @@ -565,74 +523,67 @@ mod tests { let mut mock_deserializer = MockDeserializer::new(); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| { - Err(DeserializerError::UnexpectedEvent { - expected_event_name: "text".to_string(), - found_event: Event::Start(BytesStart::new("ptype")), - }) + mock_deserializer + .expect_de_text() + .returning(|| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "text".to_string(), + found_event: "ptype".to_string(), }) - .times(1); - } - - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("GLchar".to_string())) - .times(1); - } + }) + .times(1); - unsafe { - mock_deserializer - .expect_de_tag_with::>() + mock_deserializer + .expect_de_tag_with::>() .with( eq("ptype"), function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), always(), ) - .returning(|deserializer, tag_name, _, func| { - func(&BytesStart::new(tag_name), deserializer) + .returning(|tag_name, _, func| { + let mut inner_mock_deserializer = MockDeserializer::new(); + + inner_mock_deserializer + .expect_de_text() + .returning(|| Ok("GLchar".to_string())) + .times(1); + + Ok(func( + &TagStart::new(tag_name), + &mut inner_mock_deserializer, + )?) }) .times(1); - } - - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("*".to_string())) - .times(1); - } - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("source".to_string())) - .times(1); - } + mock_deserializer + .expect_de_text() + .returning(|| Ok("*".to_string())) + .times(1); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_tag_with::>() + mock_deserializer + .expect_de_tag_with::>() .with( eq("name"), function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), always(), ) - .returning(|deserializer, tag_name, _, func| { - func(&BytesStart::new(tag_name), deserializer) + .returning(|tag_name, _, func| { + let mut inner_mock_deserializer = MockDeserializer::new(); + + inner_mock_deserializer + .expect_de_text() + .returning(|| Ok("source".to_string())) + .times(1); + + Ok(func( + &TagStart::new(tag_name), + &mut inner_mock_deserializer, + )?) }) .times(1); - } let parameter = - Parameter::deserialize(&BytesStart::new("param"), &mut mock_deserializer) + Parameter::deserialize(&TagStart::new("param"), &mut mock_deserializer) .expect("Expected Ok"); assert_str_eq!(parameter.name, "source"); @@ -644,70 +595,62 @@ mod tests { let mut mock_deserializer = MockDeserializer::new(); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("const".to_string())) - .times(1); - } - - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("GLchar".to_string())) - .times(1); - } + mock_deserializer + .expect_de_text() + .returning(|| Ok("const".to_string())) + .times(1); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_tag_with::>() + mock_deserializer + .expect_de_tag_with::>() .with( eq("ptype"), function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), always(), ) - .returning(|deserializer, tag_name, _, func| { - func(&BytesStart::new(tag_name), deserializer) + .returning(|tag_name, _, func| { + let mut inner_mock_deserializer = MockDeserializer::new(); + + inner_mock_deserializer + .expect_de_text() + .returning(|| Ok("GLchar".to_string())) + .times(1); + + Ok(func( + &TagStart::new(tag_name), + &mut inner_mock_deserializer, + )?) }) .times(1); - } - - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("*".to_string())) - .times(1); - } - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("name".to_string())) - .times(1); - } + mock_deserializer + .expect_de_text() + .returning(|| Ok("*".to_string())) + .times(1); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_tag_with::>() + mock_deserializer + .expect_de_tag_with::>() .with( eq("name"), function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), always(), ) - .returning(|deserializer, tag_name, _, func| { - func(&BytesStart::new(tag_name), deserializer) + .returning(|tag_name, _, func| { + let mut inner_mock_deserializer = MockDeserializer::new(); + + inner_mock_deserializer + .expect_de_text() + .returning(|| Ok("name".to_string())) + .times(1); + + Ok(func( + &TagStart::new(tag_name), + &mut inner_mock_deserializer, + )?) }) .times(1); - } let parameter = - Parameter::deserialize(&BytesStart::new("param"), &mut mock_deserializer) + Parameter::deserialize(&TagStart::new("param"), &mut mock_deserializer) .expect("Expected Ok"); assert_str_eq!(parameter.name, "name"); @@ -719,70 +662,60 @@ mod tests { let mut mock_deserializer = MockDeserializer::new(); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("void *".to_string())) - .times(1); - } + mock_deserializer + .expect_de_text() + .returning(|| Ok("void *".to_string())) + .times(1); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_tag_with::>() + mock_deserializer + .expect_de_tag_with::>() .with( eq("ptype"), function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), always(), ) - .returning(|_, _, _, _| { + .returning(|_, _, _| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "start".to_string(), - found_event: Event::Start(BytesStart::new("name")), + found_event: "name".to_string(), }) }) .times(1); - } - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| { - Err(DeserializerError::UnexpectedEvent { - expected_event_name: "text".to_string(), - found_event: Event::Start(BytesStart::new("name")), - }) + mock_deserializer + .expect_de_text() + .returning(|| { + Err(DeserializerError::UnexpectedEvent { + expected_event_name: "text".to_string(), + found_event: "start".to_string(), }) - .times(1); - } - - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_text() - .returning(|_| Ok("pixels".to_string())) - .times(1); - } + }) + .times(1); - // SAFETY: No argument lifetime is mistreated - unsafe { - mock_deserializer - .expect_de_tag_with::>() + mock_deserializer + .expect_de_tag_with::>() .with( eq("name"), function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), always(), ) - .returning(|deserializer, tag_name, _, func| { - func(&BytesStart::new(tag_name), deserializer) + .returning(|tag_name, _, func| { + let mut inner_mock_deserializer = MockDeserializer::new(); + + inner_mock_deserializer + .expect_de_text() + .returning(|| Ok("pixels".to_string())) + .times(1); + + Ok(func( + &TagStart::new(tag_name), + &mut inner_mock_deserializer, + )?) }) .times(1); - } let parameter = - Parameter::deserialize(&BytesStart::new("param"), &mut mock_deserializer) + Parameter::deserialize(&TagStart::new("param"), &mut mock_deserializer) .expect("Expected Ok"); assert_str_eq!(parameter.name, "pixels"); diff --git a/src/deserialization/buffer_deserializer.rs b/src/deserialization/buffer_deserializer.rs deleted file mode 100644 index a4e7b2f..0000000 --- a/src/deserialization/buffer_deserializer.rs +++ /dev/null @@ -1,240 +0,0 @@ -use std::any::type_name; -use std::error::Error; -use std::io::BufRead; - -use quick_xml::events::{BytesStart, Event}; -use quick_xml::Reader; - -use crate::deserialization::{ - Deserialize, - Deserializer, - DeserializerError, - IgnoreEnd, - WrappedDeserializeError, -}; - -macro_rules! read_event { - ($self: ident) => {{ - let event = if let Some(leftover_event) = $self.leftover_event.take() { - leftover_event - } else { - $self.reader.read_event_into(&mut $self.buf)?.into_owned() - }; - - if let Event::Eof = &event { - return Err(DeserializerError::UnexpectedEndOfFile); - } - - event - }}; -} - -pub struct BufferDeserializer -{ - reader: Reader, - leftover_event: Option>, - buf: Vec, -} - -impl BufferDeserializer -where - Source: BufRead, -{ - pub fn new(source: Source) -> Self - { - let mut reader = Reader::from_reader(source); - - reader.trim_text(true); - reader.expand_empty_elements(true); - - Self { - reader, - leftover_event: None, - buf: Vec::new(), - } - } -} - -impl Deserializer for BufferDeserializer -where - Source: BufRead, -{ - fn de_tag( - &mut self, - tag_name: &str, - ignore_end: IgnoreEnd, - ) -> Result - { - self.de_tag_with(tag_name, ignore_end, De::deserialize) - } - - fn de_tag_with( - &mut self, - tag_name: &str, - ignore_end: IgnoreEnd, - deserialize: DeserializeFn, - ) -> Result - where - Err: Error + Send + Sync + 'static, - DeserializeFn: FnOnce(&BytesStart, &mut Self) -> Result, - { - let deserialized = match read_event!(self) { - Event::Start(start) if start.name().as_ref() == tag_name.as_bytes() => { - deserialize(&start, self).map_err(|err| { - DeserializerError::DeserializeFailed( - type_name::(), - WrappedDeserializeError::new(err), - ) - })? - } - event => { - self.leftover_event = Some(event.clone().into_owned()); - - return Err(DeserializerError::UnexpectedEvent { - expected_event_name: format!("start({tag_name})"), - found_event: event, - }); - } - }; - - if let IgnoreEnd::No = ignore_end { - self.read_end_event(tag_name.as_bytes())?; - } - - Ok(deserialized) - } - - fn de_tag_list( - &mut self, - tag_name: &str, - ) -> Result, DeserializerError> - { - let mut deserialized_items = Vec::new(); - - loop { - let start = match read_event!(self) { - Event::Start(start) if start.name().as_ref() == tag_name.as_bytes() => { - start - } - Event::Comment(_) => { - continue; - } - event => { - self.leftover_event = Some(event.into_owned()); - break; - } - }; - - let deserialized = De::deserialize(&start, self).map_err(|err| { - DeserializerError::DeserializeFailed( - type_name::(), - WrappedDeserializeError::new(err), - ) - })?; - - self.read_end_event(tag_name.as_bytes())?; - - deserialized_items.push(deserialized); - } - - Ok(deserialized_items) - } - - fn de_list(&mut self) -> Result, DeserializerError> - { - let mut deserialized_items = Vec::new(); - - loop { - let start = match read_event!(self) { - Event::Start(start) => start, - Event::Comment(_) => { - continue; - } - event => { - self.leftover_event = Some(event.into_owned()); - break; - } - }; - - let deserialized = De::deserialize(&start, self).map_err(|err| { - DeserializerError::DeserializeFailed( - type_name::(), - WrappedDeserializeError::new(err), - ) - })?; - - self.read_end_event(start.name().as_ref())?; - - deserialized_items.push(deserialized); - } - - Ok(deserialized_items) - } - - fn de_text(&mut self) -> Result - { - let text = match read_event!(self) { - Event::Text(text) => Ok(text), - event => { - self.leftover_event = Some(event.clone().into_owned()); - - Err(DeserializerError::UnexpectedEvent { - expected_event_name: "text".to_string(), - found_event: event, - }) - } - }? - .unescape()?; - - Ok(text.to_string()) - } - - fn skip_to_tag_start(&mut self, tag_name: &str) -> Result<(), DeserializerError> - { - loop { - match read_event!(self) { - Event::Start(start) if start.name().as_ref() == tag_name.as_bytes() => { - self.leftover_event = Some(Event::Start(start).into_owned()); - - break; - } - _ => {} - } - } - - Ok(()) - } - - fn skip_to_tag_end(&mut self, tag_name: &str) -> Result<(), DeserializerError> - { - loop { - match read_event!(self) { - Event::End(end) if end.name().as_ref() == tag_name.as_bytes() => { - self.leftover_event = Some(Event::End(end).into_owned()); - - return Ok(()); - } - _ => {} - } - } - } -} - -impl BufferDeserializer -where - Source: BufRead, -{ - fn read_end_event(&mut self, tag_name: &[u8]) -> Result<(), DeserializerError> - { - let event = read_event!(self); - - if matches!(&event, Event::End(end) if end.name().as_ref() == tag_name) { - return Ok(()); - } - - Err(DeserializerError::UnexpectedEvent { - expected_event_name: "end".to_string(), - found_event: event.into_owned(), - }) - } -} diff --git a/src/deserialization/mod.rs b/src/deserialization/mod.rs deleted file mode 100644 index b86c2af..0000000 --- a/src/deserialization/mod.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::error::Error; -use std::ops::Deref; - -use quick_xml::events::{BytesStart, Event}; - -pub mod buffer_deserializer; - -pub trait Deserialize: Sized -{ - type Error: Error + Send + Sync + 'static; - - fn deserialize( - start: &BytesStart, - deserializer: &mut TDeserializer, - ) -> Result; -} - -#[cfg_attr(test, ridicule::automock)] -pub trait 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: DeserializeFn, - ) -> Result - where - Err: Error + Send + Sync + 'static, - DeserializeFn: FnOnce(&BytesStart, &mut Self) -> Result; - - fn de_tag_list( - &mut self, - tag_name: &str, - ) -> Result, DeserializerError>; - - fn de_list(&mut self) -> Result, DeserializerError>; - - fn de_text(&mut self) -> Result; - - fn skip_to_tag_start(&mut self, tag_name: &str) -> Result<(), DeserializerError>; - - fn skip_to_tag_end(&mut self, tag_name: &str) -> Result<(), DeserializerError>; -} - -pub enum IgnoreEnd -{ - Yes, - No, -} - -/// Function pointer type passable to [`Deserializer::de_tag_with`]. -pub type DeserializeWithFn = - fn(&BytesStart, &mut Deserializer) -> Result; - -#[derive(Debug, thiserror::Error)] -pub enum DeserializerError -{ - #[error("Failed to read")] - ReadFailed(#[from] quick_xml::Error), - - #[error("Failed to deserialize {0}")] - DeserializeFailed(&'static str, #[source] WrappedDeserializeError), - - #[error("Expected {expected_event_name} event. Found {found_event:?}")] - UnexpectedEvent - { - expected_event_name: String, - found_event: Event<'static>, - }, - - #[error("Unexpected end of file")] - UnexpectedEndOfFile, -} - -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct WrappedDeserializeError(Box); - -impl WrappedDeserializeError -{ - fn new(err: Err) -> Self - { - Self(Box::new(err)) - } -} - -impl Deref for WrappedDeserializeError -{ - type Target = dyn Error; - - fn deref(&self) -> &Self::Target - { - self.0.as_ref() - } -} - -pub trait ResultExt -{ - fn try_event(self) -> Result, DeserializerError>; -} - -impl ResultExt for Result -{ - fn try_event(self) -> Result, DeserializerError> - { - self.map_or_else( - |err| { - if let DeserializerError::UnexpectedEvent { - expected_event_name: _, - found_event: _, - } = err - { - return Ok(None); - } - - Err(err) - }, - |value| Ok(Some(value)), - ) - } -} diff --git a/src/lib.rs b/src/lib.rs index 4e33f8c..8a992b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,20 +21,33 @@ #![cfg_attr(doc_cfg, feature(doc_cfg))] #![deny(clippy::all, clippy::pedantic, missing_docs)] +use std::convert::Infallible; use std::fs::File; use std::io::Read; -use quick_xml::events::BytesStart; - -use crate::api_interface_definition::APIInterfaceDefinition; +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::{ + impl_from_deserializer_error, + xml_stinks_if_deserializer_static_generics, + DeserializeTagged, +}; + +use crate::api_interface_definition::{ + APIInterfaceDefinition, + Error as APIInterfaceDefinitionError, +}; use crate::command::{Command, Error as CommandError}; -use crate::deserialization::buffer_deserializer::BufferDeserializer; -use crate::deserialization::{Deserialize, Deserializer, DeserializerError, IgnoreEnd}; +use crate::util::impl_from_deserializer_err_wrapped; pub mod api_interface_definition; pub mod command; -mod deserialization; +mod util; + +#[cfg(test)] +mod test_utils; /// XML. #[cfg(feature = "include-xml")] @@ -69,7 +82,17 @@ impl Registry /// Returns `Err` if parsing fails in any way. pub fn retrieve_from_bytes(xml_bytes: &[u8]) -> Result { - let mut deserializer = BufferDeserializer::new(xml_bytes); + let mut deserializer = xml_stinks_if_deserializer_static_generics!(then { + // The deserializer-static-generics feature of the dependency xml-stinks is + // enabled. + // + // The feature is enabled when building tests and is required for mocking to + // work. However, it will reject any non-owned source, so the bytes has to be + // made into a Cursor> + BufferedDeserializer::new(std::io::Cursor::new(xml_bytes.to_vec())); + } else { + BufferedDeserializer::new(xml_bytes); + }); deserializer.skip_to_tag_start(REGISTRY_TAG_NAME)?; @@ -125,12 +148,12 @@ impl Registry } } -impl Deserialize for Registry +impl DeserializeTagged for Registry { type Error = RegistryError; fn deserialize( - _start: &BytesStart, + _start: &TagStart, deserializer: &mut TDeserializer, ) -> Result { @@ -138,13 +161,13 @@ impl Deserialize for Registry let commands = deserializer.de_tag_with("commands", IgnoreEnd::No, |_, deserializer| { - deserializer.de_tag_list::("command") + deserializer.de_tag_list::(Some("command")) })?; deserializer.skip_to_tag_start("feature")?; let api_interface_definitions = - deserializer.de_tag_list::("feature")?; + deserializer.de_tag_list::(Some("feature"))?; Ok(Self { commands, @@ -165,10 +188,14 @@ pub enum RegistryError #[error("No 'commands' element was found")] MissingCommandsElement, - /// A command is invalid. + /// Invalid command. #[error("Invalid command")] InvalidCommand(#[from] CommandError), + /// Invalid API interface definition. + #[error("Invalid API interface definition")] + InvalidAPIInterfaceDefinition(#[from] APIInterfaceDefinitionError), + /// I/O failed. #[error("I/O failed")] IOFailed(#[from] std::io::Error), @@ -178,18 +205,13 @@ pub enum RegistryError DeserializationFailed(#[from] DeserializationError), } -impl From for RegistryError -{ - fn from(err: DeserializerError) -> Self - { - DeserializationError(err).into() - } -} +impl_from_deserializer_err_wrapped!(RegistryError); +impl_from_deserializer_error!(RegistryError); /// Deserialization error. #[derive(Debug, thiserror::Error)] #[error(transparent)] -pub struct DeserializationError(#[from] DeserializerError); +pub struct DeserializationError(#[from] DeserializerError); #[cfg(test)] mod tests diff --git a/src/test_utils.rs b/src/test_utils.rs new file mode 100644 index 0000000..0f80cb7 --- /dev/null +++ b/src/test_utils.rs @@ -0,0 +1,44 @@ +use std::convert::Infallible; + +use mockall::mock; +use xml_stinks::deserializer::{Deserializer, Error, IgnoreEnd, MaybeStatic}; +use xml_stinks::tagged::TagStart; +use xml_stinks::DeserializeTagged; + +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>; + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..fd1f767 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,15 @@ +macro_rules! impl_from_deserializer_err_wrapped { + ($err: ident) => { + impl From<::xml_stinks::deserializer::Error<::std::convert::Infallible>> for $err + { + fn from( + err: ::xml_stinks::deserializer::Error<::std::convert::Infallible>, + ) -> Self + { + Self::DeserializationFailed(err.into()) + } + } + }; +} + +pub(crate) use impl_from_deserializer_err_wrapped; -- cgit v1.2.3-18-g5258