//! OpenGL command. 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. #[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 DeserializeTagged for Command { type Error = Error; fn deserialize( start: &TagStart, deserializer: &mut TDeserializer, ) -> Result { let prototype = deserializer.de_tag::("proto", IgnoreEnd::No)?; let parameters = deserializer.de_tag_list::(Some("param"))?; deserializer.skip_to_tag_end(start.name()?)?; 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), /// 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 { 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 DeserializeTagged for Prototype { type Error = PrototypeError; fn deserialize( _start: &TagStart, deserializer: &mut TDeserializer, ) -> Result { let return_type = deserialize_type::(deserializer)?; // 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 }) } } /// [`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_deserializer_err_wrapped!(PrototypeError); impl_from_deserializer_error!(PrototypeError); /// 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 DeserializeTagged for Parameter { type Error = ParameterError; fn deserialize( _start: &TagStart, deserializer: &mut TDeserializer, ) -> Result { let ty = deserialize_type::(deserializer)?; // let name = deserializer.de_tag_with::<_, _, DeserializeWithFn<_, _, _>>( let name = deserializer.de_tag_with("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_deserializer_err_wrapped!(ParameterError); impl_from_deserializer_error!(ParameterError); fn deserialize_type(deserializer: &mut impl Deserializer) -> Result where Err: From>, { let type_before = deserializer.de_text().try_event()?; //.de_tag_with::<_, _, DeserializeWithFn<_, _, _>>( let type_ptype = deserializer .de_tag_with("ptype", IgnoreEnd::No, |_, deserializer| { deserializer.de_text() }) .try_event() .map_err(Into::into)?; 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 mockall::predicate::{always, eq, function}; use pretty_assertions::assert_str_eq; use super::*; use crate::test_utils::MockDeserializer; #[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: "ptype".to_string(), }) }) .times(1); mock_deserializer .expect_de_tag_with::>() .with( eq("ptype"), 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("GLuint".to_string())) .times(1); Ok(func( &TagStart::new(tag_name), &mut inner_mock_deserializer, )?) }) .times(1); mock_deserializer .expect_de_text() .returning(|| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "text".to_string(), found_event: "name".to_string(), }) }) .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(&TagStart::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: "name".to_string(), }) }) .times(1); mock_deserializer .expect_de_text() .returning(|| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "text".to_string(), found_event: "name".to_string(), }) }) .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("glDoSomeThing".to_string())) .times(1); Ok(func( &TagStart::new(tag_name), &mut inner_mock_deserializer, )?) }) .times(1); let prototype = Prototype::deserialize(&TagStart::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: "ptype".to_string(), }) }) .times(1); mock_deserializer .expect_de_tag_with::>() .with( eq("ptype"), 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("GLenum".to_string())) .times(1); Ok(func( &TagStart::new(tag_name), &mut inner_mock_deserializer, )?) }) .times(1); mock_deserializer .expect_de_text() .returning(|| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "text".to_string(), found_event: "name".to_string(), }) }) .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("value".to_string())) .times(1); Ok(func( &TagStart::new(tag_name), &mut inner_mock_deserializer, )?) }) .times(1); let parameter = Parameter::deserialize(&TagStart::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: "ptype".to_string(), }) }) .times(1); mock_deserializer .expect_de_tag_with::>() .with( eq("ptype"), 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("GLchar".to_string())) .times(1); Ok(func( &TagStart::new(tag_name), &mut inner_mock_deserializer, )?) }) .times(1); mock_deserializer .expect_de_text() .returning(|| Ok("*".to_string())) .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("source".to_string())) .times(1); Ok(func( &TagStart::new(tag_name), &mut inner_mock_deserializer, )?) }) .times(1); let parameter = Parameter::deserialize(&TagStart::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_tag_with::>() .with( eq("ptype"), 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("GLchar".to_string())) .times(1); Ok(func( &TagStart::new(tag_name), &mut inner_mock_deserializer, )?) }) .times(1); mock_deserializer .expect_de_text() .returning(|| Ok("*".to_string())) .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("name".to_string())) .times(1); Ok(func( &TagStart::new(tag_name), &mut inner_mock_deserializer, )?) }) .times(1); let parameter = Parameter::deserialize(&TagStart::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: "name".to_string(), }) }) .times(1); mock_deserializer .expect_de_text() .returning(|| { Err(DeserializerError::UnexpectedEvent { expected_event_name: "text".to_string(), found_event: "start".to_string(), }) }) .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("pixels".to_string())) .times(1); Ok(func( &TagStart::new(tag_name), &mut inner_mock_deserializer, )?) }) .times(1); let parameter = Parameter::deserialize(&TagStart::new("param"), &mut mock_deserializer) .expect("Expected Ok"); assert_str_eq!(parameter.name, "pixels"); assert_str_eq!(parameter.ty, "void *"); } }