//! OpenGL command. use quick_xml::events::BytesStart; use crate::deserialization::{ Deserialize, DeserializeWithFn, Deserializer, DeserializerError, IgnoreEnd, ResultExt, }; use crate::DeserializationError; /// A command. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Command { prototype: Prototype, parameters: Vec, } impl Command { /// Returns a new `Command`. pub fn new( prototype: Prototype, parameters: impl IntoIterator, ) -> Self { Self { prototype, parameters: parameters.into_iter().collect(), } } /// Returns the command prototype. #[must_use] pub fn prototype(&self) -> &Prototype { &self.prototype } /// Returns the command parameters. #[must_use] pub fn parameters(&self) -> &[Parameter] { &self.parameters } } impl Deserialize for Command { type Error = Error; fn deserialize( start: &BytesStart, deserializer: &mut TDeserializer, ) -> Result { let prototype = deserializer.de_tag::("proto", IgnoreEnd::No)?; let parameters = deserializer.de_tag_list::("param")?; deserializer .skip_to_tag_end(std::str::from_utf8(start.name().as_ref()).unwrap())?; Ok(Self { prototype, parameters, }) } } /// [`Command`] error. #[derive(Debug, thiserror::Error)] pub enum Error { /// No 'proto' element was found. #[error("No 'proto' element was found")] MissingPrototype, /// Invalid prototype. #[error("Invalid prototype")] InvalidPrototype(#[from] PrototypeError), /// Invalid parameter. #[error("Invalid parameter")] InvalidParameter(#[from] ParameterError), /// Deserialization failed. #[error("Deserialization failed")] DeserializationFailed(#[from] DeserializationError), } impl From for Error { fn from(err: DeserializerError) -> Self { DeserializationError(err).into() } } /// A command prototype. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Prototype { name: String, return_type: String, } impl Prototype { /// Returns a new `Prototype`. pub fn new(name: impl Into, return_type: impl Into) -> Self { Self { name: name.into(), return_type: return_type.into(), } } /// Returns the command prototype name. #[must_use] pub fn name(&self) -> &str { &self.name } /// Returns the command prototype return type. #[must_use] pub fn return_type(&self) -> &str { &self.return_type } } impl Deserialize for Prototype { type Error = PrototypeError; fn deserialize( _start: &BytesStart, deserializer: &mut TDeserializer, ) -> Result { let return_type = deserialize_type::(deserializer)?; let name = deserializer.de_tag_with::<_, _, DeserializeWithFn<_, _, _>>( "name", IgnoreEnd::No, |_, deserializer| deserializer.de_text(), )?; Ok(Self { name, return_type }) } } /// [`Prototype`] error. #[derive(Debug, thiserror::Error)] pub enum PrototypeError { /// No 'name' element was found. #[error("No 'name' element was found")] MissingName, /// No return type was found. #[error("No return type was found")] MissingReturnType, /// Deserialization failed. #[error("Deserialization failed")] DeserializationFailed(#[from] DeserializationError), } impl From for PrototypeError { fn from(err: DeserializerError) -> Self { DeserializationError(err).into() } } /// A command parameter. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Parameter { name: String, ty: String, } impl Parameter { /// Returns a new `Parameter`. pub fn new(name: impl Into, ty: impl Into) -> Self { Self { name: name.into(), ty: ty.into(), } } /// Returns the name of the command parameter. #[must_use] pub fn name(&self) -> &str { &self.name } /// Returns the type of the command parameter. #[must_use] pub fn get_type(&self) -> &str { &self.ty } } impl Deserialize for Parameter { type Error = ParameterError; fn deserialize( _start: &BytesStart, deserializer: &mut TDeserializer, ) -> Result { let ty = deserialize_type::(deserializer)?; let name = deserializer.de_tag_with::<_, _, DeserializeWithFn<_, _, _>>( "name", IgnoreEnd::No, |_, deserializer| deserializer.de_text(), )?; Ok(Self { name, ty }) } } /// [`Parameter`] error. #[derive(Debug, thiserror::Error)] pub enum ParameterError { /// No 'name' element was found. #[error("No 'name' element was found")] MissingName, /// Deserialization failed. #[error("Deserialization failed")] DeserializationFailed(#[from] DeserializationError), } impl From for ParameterError { fn from(err: DeserializerError) -> Self { DeserializationError(err).into() } } fn deserialize_type(deserializer: &mut impl Deserializer) -> Result where Err: From, { let type_before = deserializer.de_text().try_event()?; let type_ptype = deserializer .de_tag_with::<_, _, DeserializeWithFn<_, _, _>>( "ptype", IgnoreEnd::No, |_, deserializer| deserializer.de_text(), ) .try_event()?; let type_after = deserializer.de_text().try_event()?; let type_before_after = [type_before.clone(), type_after.clone()] .into_iter() .flatten(); Ok(type_ptype.map_or_else( || join_space_strs(type_before_after), |ptype_text| { let before = type_before .map(|before| format!("{before} ")) .unwrap_or_default(); format!( "{before}{ptype_text}{}", type_after .map(|after| format!(" {after}")) .unwrap_or_default() ) }, )) } fn join_space_strs(strings: Strings) -> String where Strings: Iterator, StrItem: ToString, { strings .into_iter() .map(|string| string.to_string()) .collect::>() .join(" ") } #[cfg(test)] mod tests { use pretty_assertions::assert_str_eq; use quick_xml::events::Event; use ridicule::mock; use ridicule::predicate::{always, eq, function}; use super::*; mock! { MockDeserializer {} impl Deserializer for MockDeserializer { 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: std::error::Error + Send + Sync + 'static, DeserializeFn: FnOnce(&BytesStart, &mut MockDeserializer) -> Result; fn de_tag_list( &mut self, tag_name: &str ) -> 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>; } } #[test] fn deserialize_prototype_works_with_ptype() { let mut mock_deserializer = MockDeserializer::new(); mock_deserializer .expect_de_text() .returning(|_| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "text".to_string(), found_event: Event::Start(BytesStart::new("ptype")), }) }) .times(1); mock_deserializer .expect_de_text() .returning(|_| Ok("GLuint".to_string())) .times(1); 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) }) .times(1); mock_deserializer .expect_de_text() .returning(|_| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "text".to_string(), found_event: Event::Start(BytesStart::new("name")), }) }) .times(1); mock_deserializer .expect_de_text() .returning(|_| Ok("glDoComplicatedThing".to_string())) .times(1); 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); let prototype = Prototype::deserialize(&BytesStart::new("proto"), &mut mock_deserializer) .expect("Expected Ok"); assert_str_eq!(prototype.name, "glDoComplicatedThing"); assert_str_eq!(prototype.return_type, "GLuint"); } #[test] fn deserialize_prototype_works_with_text() { let mut mock_deserializer = MockDeserializer::new(); mock_deserializer .expect_de_text() .returning(|_| Ok("void".to_string())) .times(1); mock_deserializer .expect_de_tag_with::>() .with( eq("ptype"), function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), always(), ) .returning(|_, _, _, _| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "start".to_string(), found_event: Event::Start(BytesStart::new("name")), }) }) .times(1); mock_deserializer .expect_de_text() .returning(|_| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "text".to_string(), found_event: Event::Start(BytesStart::new("name")), }) }) .times(1); mock_deserializer .expect_de_text() .returning(|_| Ok("glDoSomeThing".to_string())) .times(1); 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); let prototype = Prototype::deserialize(&BytesStart::new("proto"), &mut mock_deserializer) .expect("Expected Ok"); assert_str_eq!(prototype.name, "glDoSomeThing"); assert_str_eq!(prototype.return_type, "void"); } #[test] fn deserialize_parameter_works_with_ptype_only() { let mut mock_deserializer = MockDeserializer::new(); mock_deserializer .expect_de_text() .returning(|_| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "text".to_string(), found_event: Event::Start(BytesStart::new("ptype")), }) }) .times(1); mock_deserializer .expect_de_text() .returning(|_| Ok("GLenum".to_string())) .times(1); 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) }) .times(1); mock_deserializer .expect_de_text() .returning(|_| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "text".to_string(), found_event: Event::Start(BytesStart::new("name")), }) }) .times(1); mock_deserializer .expect_de_text() .returning(|_| Ok("value".to_string())) .times(1); 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); let parameter = Parameter::deserialize(&BytesStart::new("param"), &mut mock_deserializer) .expect("Expected Ok"); assert_str_eq!(parameter.name, "value"); assert_str_eq!(parameter.ty, "GLenum"); } #[test] fn deserialize_parameter_works_with_ptype_and_text_after() { let mut mock_deserializer = MockDeserializer::new(); mock_deserializer .expect_de_text() .returning(|_| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "text".to_string(), found_event: Event::Start(BytesStart::new("ptype")), }) }) .times(1); mock_deserializer .expect_de_text() .returning(|_| Ok("GLchar".to_string())) .times(1); 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) }) .times(1); mock_deserializer .expect_de_text() .returning(|_| Ok("*".to_string())) .times(1); mock_deserializer .expect_de_text() .returning(|_| Ok("source".to_string())) .times(1); 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); let parameter = Parameter::deserialize(&BytesStart::new("param"), &mut mock_deserializer) .expect("Expected Ok"); assert_str_eq!(parameter.name, "source"); assert_str_eq!(parameter.ty, "GLchar *"); } #[test] fn deserialize_parameter_works_with_ptype_and_text_before_and_after() { let mut mock_deserializer = MockDeserializer::new(); mock_deserializer .expect_de_text() .returning(|_| Ok("const".to_string())) .times(1); mock_deserializer .expect_de_text() .returning(|_| Ok("GLchar".to_string())) .times(1); 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) }) .times(1); mock_deserializer .expect_de_text() .returning(|_| Ok("*".to_string())) .times(1); mock_deserializer .expect_de_text() .returning(|_| Ok("name".to_string())) .times(1); 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); let parameter = Parameter::deserialize(&BytesStart::new("param"), &mut mock_deserializer) .expect("Expected Ok"); assert_str_eq!(parameter.name, "name"); assert_str_eq!(parameter.ty, "const GLchar *"); } #[test] fn deserialize_parameter_works_with_text() { let mut mock_deserializer = MockDeserializer::new(); mock_deserializer .expect_de_text() .returning(|_| Ok("void *".to_string())) .times(1); mock_deserializer .expect_de_tag_with::>() .with( eq("ptype"), function(|ignore_end| matches!(ignore_end, IgnoreEnd::No)), always(), ) .returning(|_, _, _, _| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "start".to_string(), found_event: Event::Start(BytesStart::new("name")), }) }) .times(1); mock_deserializer .expect_de_text() .returning(|_| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "text".to_string(), found_event: Event::Start(BytesStart::new("name")), }) }) .times(1); mock_deserializer .expect_de_text() .returning(|_| Ok("pixels".to_string())) .times(1); 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); let parameter = Parameter::deserialize(&BytesStart::new("param"), &mut mock_deserializer) .expect("Expected Ok"); assert_str_eq!(parameter.name, "pixels"); assert_str_eq!(parameter.ty, "void *"); } }