//! GL API interface definition. use quick_xml::events::BytesStart; use crate::deserialization::{Deserialize, Deserializer, DeserializerError}; use crate::DeserializationError; /// GL API interface definition. #[derive(Debug, PartialEq, Eq)] pub struct APIInterfaceDefinition { api_name: String, name: String, version: String, requirements: Vec, removals: Vec, } impl APIInterfaceDefinition { /// Returns a new `APIInterfaceDefinition`. #[must_use] pub fn new( api_name: impl Into, name: impl Into, version: impl Into, requirements: impl IntoIterator, removals: impl IntoIterator, ) -> Self { Self { api_name: api_name.into(), name: name.into(), version: version.into(), requirements: requirements.into_iter().collect(), removals: removals.into_iter().collect(), } } /// Returns the API name. #[must_use] pub fn api_name(&self) -> &str { &self.api_name } /// Returns the name. #[must_use] pub fn name(&self) -> &str { &self.name } /// Returns the version. #[must_use] pub fn version(&self) -> &str { &self.version } /// Returns the requirements. #[must_use] pub fn requirements(&self) -> &[Requirement] { &self.requirements } /// Returns the removals. #[must_use] pub fn removals(&self) -> &[Removal] { &self.removals } } impl Deserialize for APIInterfaceDefinition { type Error = Error; fn deserialize( start: &BytesStart, 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")?; Ok(Self { api_name, name, version, requirements, removals, }) } } /// [`APIInterfaceDefinition`] error. #[derive(Debug, thiserror::Error)] pub enum Error { /// Missing API name attribute. #[error("Missing API name attribute")] MissingAPIName, /// Missing name attribute. #[error("Missing name attribute")] MissingName, /// Missing version number attribute. #[error("Missing version number attribute")] MissingVersionNumber, /// API name is not valid UTF-8. #[error("API name is not valid UTF-8")] APINameNotUTF8, /// Name is not valid UTF-8. #[error("Name is not valid UTF-8")] NameNotUTF8, /// Version is not valid UTF-8. #[error("Version is not valid UTF-8")] VersionNotUTF8, /// Deserialization failed. #[error("Deserialization failed")] DeserializationFailed(#[from] DeserializationError), } impl From for Error { fn from(err: DeserializerError) -> Self { DeserializationError(err).into() } } /// GL API feature requirement. #[derive(Debug, PartialEq, Eq)] pub struct Requirement { profile: Option, comment: Option, features: Vec, } impl Requirement { /// Returns a new `Requirement`. #[must_use] pub fn new( profile: Option, comment: Option, features: impl IntoIterator, ) -> Self { Self { profile, comment, features: features.into_iter().collect(), } } /// Returns the required features. #[must_use] pub fn features(&self) -> &[Feature] { &self.features } /// Returns the profile. #[must_use] pub fn profile(&self) -> Option<&str> { self.profile.as_deref() } /// Returns the comment. #[must_use] pub fn comment(&self) -> Option<&str> { self.comment.as_deref() } } impl Deserialize for Requirement { type Error = RequirementError; fn deserialize( start: &BytesStart, deserializer: &mut TDeserializer, ) -> Result { let profile = match start .try_get_attribute(b"profile") .map_err(DeserializerError::ReadFailed)? { Some(comment_attr) => Some( String::from_utf8(comment_attr.value.into_owned()) .map_err(|_| RequirementError::ProfileNotUTF8)?, ), None => None, }; 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(|_| RequirementError::CommentNotUTF8)?, ), None => None, }; let features = deserializer.de_list::()?; Ok(Self { profile, comment, features, }) } } /// [`Requirement`] error. #[derive(Debug, thiserror::Error)] pub enum RequirementError { /// Profile is not valid UTF-8. #[error("Profile is not valid UTF-8")] ProfileNotUTF8, /// Comment is not valid UTF-8. #[error("Comment is not valid UTF-8")] CommentNotUTF8, /// Deserialization failed. #[error("Deserialization failed")] DeserializationFailed(#[from] DeserializationError), } impl From for RequirementError { fn from(err: DeserializerError) -> Self { DeserializationError(err).into() } } /// GL API feature removal. #[derive(Debug, PartialEq, Eq)] pub struct Removal { profile: String, comment: String, features: Vec, } impl Removal { /// Returns a new `Removal`. #[must_use] pub fn new( profile: impl Into, comment: impl Into, features: impl IntoIterator, ) -> Self { Self { profile: profile.into(), comment: comment.into(), features: features.into_iter().collect(), } } /// Returns the removed features. #[must_use] pub fn features(&self) -> &[Feature] { &self.features } /// Returns the profile. #[must_use] pub fn profile(&self) -> &str { &self.profile } /// Returns the comment. #[must_use] pub fn comment(&self) -> &str { &self.comment } } impl Deserialize for Removal { type Error = RemovalError; fn deserialize( start: &BytesStart, 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::()?; Ok(Self { profile, comment, features, }) } } /// [`Removal`] error. #[derive(Debug, thiserror::Error)] pub enum RemovalError { /// Missing profile attribute. #[error("Missing profile attribute")] MissingProfile, /// Missing comment attribute. #[error("Missing comment attribute")] MissingComment, /// Profile is not valid UTF-8. #[error("Profile is not valid UTF-8")] ProfileNotUTF8, /// Comment is not valid UTF-8. #[error("Comment is not valid UTF-8")] CommentNotUTF8, /// Deserialization failed. #[error("Deserialization failed")] DeserializationFailed(#[from] DeserializationError), } impl From for RemovalError { fn from(err: DeserializerError) -> Self { DeserializationError(err).into() } } /// GL API feature. #[derive(Debug, PartialEq, Eq)] pub struct Feature { kind: FeatureKind, name: String, comment: Option, } impl Feature { /// Returns a new `Feature`. #[must_use] pub fn new( kind: FeatureKind, name: impl Into, comment: Option, ) -> Self { Self { kind, name: name.into(), comment, } } /// Returns the feature kind. #[must_use] pub fn kind(&self) -> FeatureKind { self.kind } /// Returns the feature name. #[must_use] pub fn name(&self) -> &str { &self.name } /// Returns the feature comment. #[must_use] pub fn comment(&self) -> Option<&str> { self.comment.as_deref() } } impl Deserialize for Feature { type Error = FeatureError; fn deserialize( start: &BytesStart, _deserializer: &mut TDeserializer, ) -> Result { let kind = match start.name().as_ref() { b"enum" => Ok(FeatureKind::Enum), b"command" => Ok(FeatureKind::Command), b"type" => Ok(FeatureKind::Type), unknown_kind => Err(FeatureError::UnknownFeatureKind( String::from_utf8_lossy(unknown_kind).into_owned(), )), }?; 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, }; Ok(Self { kind, name, comment, }) } } /// [`Feature`] error. #[derive(Debug, thiserror::Error)] pub enum FeatureError { /// Unknown feature kind. #[error("Unknown feature kind {0}")] UnknownFeatureKind(String), /// Missing name attribute. #[error("Missing name attribute")] MissingName, /// Name is not valid UTF-8. #[error("Name is not valid UTF-8")] NameNotUTF8, /// Comment is not valid UTF-8. #[error("Comment is not valid UTF-8")] CommentNotUTF8, /// Deserialization failed. #[error("Deserialization failed")] DeserializationFailed(#[from] DeserializationError), } impl From for FeatureError { fn from(err: DeserializerError) -> Self { DeserializationError(err).into() } } /// GL API feature kind. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FeatureKind { /// A enum. Enum, /// A command. Command, /// A type. Type, } #[cfg(test)] mod tests { use pretty_assertions::{assert_eq, assert_str_eq}; use super::*; use crate::deserialization::MockDeserializer; #[test] fn deserialize_feature_works() { let mut mock_deserializer = MockDeserializer::new(); let commentless_enum_feature = Feature::deserialize( &BytesStart::new("enum").with_attributes([("name", "GL_BLEND")]), &mut mock_deserializer, ) .expect("Expected Ok"); assert_eq!(commentless_enum_feature.kind, FeatureKind::Enum); assert_str_eq!(commentless_enum_feature.name, "GL_BLEND"); 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"), ]), &mut mock_deserializer, ) .expect("Expected Ok"); assert_eq!(enum_feature.kind, FeatureKind::Enum); assert_str_eq!(enum_feature.name, "GL_VERTEX_BINDING_BUFFER"); assert_str_eq!( enum_feature.comment.expect("Expected Some"), "Added in 2013/10/22 update to the spec" ); let command_feature = Feature::deserialize( &BytesStart::new("command").with_attributes([("name", "glCreateBuffers")]), &mut mock_deserializer, ) .expect("Expected Ok"); assert_eq!(command_feature.kind, FeatureKind::Command); assert_str_eq!(command_feature.name, "glCreateBuffers"); assert!(command_feature.comment.is_none()); let type_feature = Feature::deserialize( &BytesStart::new("type").with_attributes([("name", "GLbyte")]), &mut mock_deserializer, ) .expect("Expected Ok"); assert_eq!(type_feature.kind, FeatureKind::Type); assert_str_eq!(type_feature.name, "GLbyte"); assert!(type_feature.comment.is_none()); assert!(matches!( Feature::deserialize(&BytesStart::new("foo"), &mut mock_deserializer), Err(FeatureError::UnknownFeatureKind(_)) )); } #[test] fn deserialize_removal_works() { let mut mock_deserializer = MockDeserializer::new(); // SAFETY: No argument lifetime is mistreated unsafe { mock_deserializer.expect_de_list().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"), ( "comment", "Compatibility-only GL 1.0 features removed from GL 3.2", ), ]), &mut mock_deserializer, ) .expect("Expected Ok"); assert_str_eq!(removal.profile, "core"); assert_str_eq!( removal.comment, "Compatibility-only GL 1.0 features removed from GL 3.2" ); assert_eq!(removal.features.len(), 2); } #[test] fn deserialize_requirement_works() { let mut mock_deserializer = MockDeserializer::new(); // SAFETY: No argument lifetime is mistreated unsafe { mock_deserializer.expect_de_list().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(|_| { 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(|_| { 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) .expect("Expected Ok"); assert!(requirement.profile.is_none()); assert!(requirement.comment.is_none()); assert_eq!(requirement.features.len(), 2); let requirement = Requirement::deserialize( &BytesStart::new("require").with_attributes([("profile", "compatibility")]), &mut mock_deserializer, ) .expect("Expected Ok"); assert_str_eq!(requirement.profile.expect("Expected Some"), "compatibility"); assert!(requirement.comment.is_none()); assert_eq!(requirement.features.len(), 3); let requirement = Requirement::deserialize( &BytesStart::new("require").with_attributes([ ("profile", "core"), ( "comment", concat!( "Restore functionality removed in GL 3.2 core ", "to GL 4.3. Needed for debug interface." ), ), ]), &mut mock_deserializer, ) .expect("Expected Ok"); assert_str_eq!(requirement.profile.expect("Expected Some"), "core"); assert_str_eq!( requirement.comment.expect("Expected Some"), concat!( "Restore functionality removed in GL 3.2 core to ", "GL 4.3. Needed for debug interface." ) ); assert_eq!(requirement.features.len(), 2); } #[test] fn deserialize_api_interface_definition_works() { 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)], ), ]) }); } // 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)], )]) }); } let api_interface_definition = APIInterfaceDefinition::deserialize( &BytesStart::new("feature").with_attributes([ ("api", "gl"), ("name", "GL_VERSION_3_2"), ("number", "3.2"), ]), &mut mock_deserializer, ) .expect("Expected Ok"); assert_str_eq!(api_interface_definition.api_name, "gl"); assert_str_eq!(api_interface_definition.name, "GL_VERSION_3_2"); assert_str_eq!(api_interface_definition.version, "3.2"); assert_eq!(api_interface_definition.requirements.len(), 2); assert_eq!(api_interface_definition.removals.len(), 1); } }