diff options
| author | HampusM <hampus@hampusmat.com> | 2023-03-26 18:50:31 +0200 | 
|---|---|---|
| committer | HampusM <hampus@hampusmat.com> | 2023-03-26 18:50:31 +0200 | 
| commit | 86f41e49f87764f9afd4be1d0d008a320dcfc331 (patch) | |
| tree | 3e7cf94bcecd42ccf2f1e8eb9928a377d4fdb1bd | |
| parent | c8e5944d697b33738c7ba01694dde5e360470043 (diff) | |
feat: add deserializing API interface definitions
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/api_interface_definition.rs | 773 | ||||
| -rw-r--r-- | src/command.rs | 35 | ||||
| -rw-r--r-- | src/deserialization/buffer_deserializer.rs | 40 | ||||
| -rw-r--r-- | src/deserialization/mod.rs | 3 | ||||
| -rw-r--r-- | src/lib.rs | 56 | 
6 files changed, 866 insertions, 43 deletions
| @@ -30,7 +30,7 @@ thiserror = "1.0.38"  [dev-dependencies]  pretty_assertions = "1.3.0"  criterion = "0.4.0" -ridicule = "0.2.0" +ridicule = { git = "https://git.hampusmat.com/ridicule" }  [[bench]]  name = "registry" diff --git a/src/api_interface_definition.rs b/src/api_interface_definition.rs new file mode 100644 index 0000000..441d3f7 --- /dev/null +++ b/src/api_interface_definition.rs @@ -0,0 +1,773 @@ +//! 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<Requirement>, +    removals: Vec<Removal>, +} + +impl APIInterfaceDefinition +{ +    /// Returns a new `APIInterfaceDefinition`. +    #[must_use] +    pub fn new( +        api_name: impl Into<String>, +        name: impl Into<String>, +        version: impl Into<String>, +        requirements: impl IntoIterator<Item = Requirement>, +        removals: impl IntoIterator<Item = Removal>, +    ) -> 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<TDeserializer: Deserializer>( +        start: &BytesStart, +        deserializer: &mut TDeserializer, +    ) -> Result<Self, Self::Error> +    { +        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::<Requirement>("require")?; + +        let removals = deserializer.de_tag_list::<Removal>("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<DeserializerError> for Error +{ +    fn from(err: DeserializerError) -> Self +    { +        DeserializationError(err).into() +    } +} + +/// GL API feature requirement. +#[derive(Debug, PartialEq, Eq)] +pub struct Requirement +{ +    profile: Option<String>, +    comment: Option<String>, +    features: Vec<Feature>, +} + +impl Requirement +{ +    /// Returns a new `Requirement`. +    #[must_use] +    pub fn new( +        profile: Option<String>, +        comment: Option<String>, +        features: impl IntoIterator<Item = Feature>, +    ) -> 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<TDeserializer: Deserializer>( +        start: &BytesStart, +        deserializer: &mut TDeserializer, +    ) -> Result<Self, Self::Error> +    { +        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::<Feature>()?; + +        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<DeserializerError> 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<Feature>, +} + +impl Removal +{ +    /// Returns a new `Removal`. +    #[must_use] +    pub fn new( +        profile: impl Into<String>, +        comment: impl Into<String>, +        features: impl IntoIterator<Item = Feature>, +    ) -> 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<TDeserializer: Deserializer>( +        start: &BytesStart, +        deserializer: &mut TDeserializer, +    ) -> Result<Self, Self::Error> +    { +        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::<Feature>()?; + +        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<DeserializerError> 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<String>, +} + +impl Feature +{ +    /// Returns a new `Feature`. +    #[must_use] +    pub fn new( +        kind: FeatureKind, +        name: impl Into<String>, +        comment: Option<String>, +    ) -> 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<TDeserializer: Deserializer>( +        start: &BytesStart, +        _deserializer: &mut TDeserializer, +    ) -> Result<Self, Self::Error> +    { +        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<DeserializerError> 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(); + +        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(); + +        mock_deserializer.expect_de_list().times(1).returning(|_| { +            Ok(vec![ +                Feature::new(FeatureKind::Command, "glNewList", None), +                Feature::new(FeatureKind::Command, "glEndList", None), +            ]) +        }); + +        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), +            ]) +        }); + +        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(); + +        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() +            .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); +    } +} diff --git a/src/command.rs b/src/command.rs index 5d3c935..fd449aa 100644 --- a/src/command.rs +++ b/src/command.rs @@ -310,43 +310,10 @@ 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<De: Deserialize>( -                &mut self, -                tag_name: &str, -                ignore_end: IgnoreEnd, -            ) -> Result<De, DeserializerError>; - -            fn de_tag_with<Output, Err, DeserializeFn>( -                &mut self, -                tag_name: &str, -                ignore_end: IgnoreEnd, -                deserialize: DeserializeFn, -            ) -> Result<Output, DeserializerError> -            where -                Err: std::error::Error + Send + Sync + 'static, -                DeserializeFn: FnOnce(&BytesStart, &mut MockDeserializer) -> Result<Output, Err>; - -            fn de_tag_list<De: Deserialize>( -                &mut self, -                tag_name: &str -            ) -> Result<Vec<De>, DeserializerError>; - -            fn de_text(&mut self) -> Result<String, DeserializerError>; - -            fn skip_to_tag_start(&mut self, tag_name: &str) -> Result<(), DeserializerError>; - -            fn skip_to_tag_end(&mut self, tag_name: &str) -> Result<(), DeserializerError>; -        } -    } +    use crate::deserialization::MockDeserializer;      #[test]      fn deserialize_prototype_works_with_ptype() diff --git a/src/deserialization/buffer_deserializer.rs b/src/deserialization/buffer_deserializer.rs index 652e1ff..a4e7b2f 100644 --- a/src/deserialization/buffer_deserializer.rs +++ b/src/deserialization/buffer_deserializer.rs @@ -98,7 +98,7 @@ where          };          if let IgnoreEnd::No = ignore_end { -            self.read_end_event(tag_name)?; +            self.read_end_event(tag_name.as_bytes())?;          }          Ok(deserialized) @@ -132,7 +132,38 @@ where                  )              })?; -            self.read_end_event(tag_name)?; +            self.read_end_event(tag_name.as_bytes())?; + +            deserialized_items.push(deserialized); +        } + +        Ok(deserialized_items) +    } + +    fn de_list<De: Deserialize>(&mut self) -> Result<Vec<De>, 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::<De>(), +                    WrappedDeserializeError::new(err), +                ) +            })?; + +            self.read_end_event(start.name().as_ref())?;              deserialized_items.push(deserialized);          } @@ -193,12 +224,11 @@ impl<Source> BufferDeserializer<Source>  where      Source: BufRead,  { -    fn read_end_event(&mut self, tag_name: &str) -> Result<(), DeserializerError> +    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.as_bytes()) -        { +        if matches!(&event, Event::End(end) if end.name().as_ref() == tag_name) {              return Ok(());          } diff --git a/src/deserialization/mod.rs b/src/deserialization/mod.rs index fa25e4b..b86c2af 100644 --- a/src/deserialization/mod.rs +++ b/src/deserialization/mod.rs @@ -15,6 +15,7 @@ pub trait Deserialize: Sized      ) -> Result<Self, Self::Error>;  } +#[cfg_attr(test, ridicule::automock)]  pub trait Deserializer  {      fn de_tag<De: Deserialize>( @@ -38,6 +39,8 @@ pub trait Deserializer          tag_name: &str,      ) -> Result<Vec<De>, DeserializerError>; +    fn de_list<De: Deserialize>(&mut self) -> Result<Vec<De>, DeserializerError>; +      fn de_text(&mut self) -> Result<String, DeserializerError>;      fn skip_to_tag_start(&mut self, tag_name: &str) -> Result<(), DeserializerError>; @@ -26,10 +26,12 @@ use std::io::Read;  use quick_xml::events::BytesStart; +use crate::api_interface_definition::APIInterfaceDefinition;  use crate::command::{Command, Error as CommandError};  use crate::deserialization::buffer_deserializer::BufferDeserializer;  use crate::deserialization::{Deserialize, Deserializer, DeserializerError, IgnoreEnd}; +pub mod api_interface_definition;  pub mod command;  mod deserialization; @@ -45,6 +47,7 @@ const REGISTRY_TAG_NAME: &str = "registry";  pub struct Registry  {      commands: Vec<Command>, +    api_interface_definitions: Vec<APIInterfaceDefinition>,  }  impl Registry @@ -96,19 +99,30 @@ impl Registry      /// # Note      /// This will **NOT** use anything from the actual OpenGL registry. Use the      /// [`Registry::retrieve`] method for that. -    pub fn new(commands: impl IntoIterator<Item = Command>) -> Self +    pub fn new( +        commands: impl IntoIterator<Item = Command>, +        api_interface_definitions: impl IntoIterator<Item = APIInterfaceDefinition>, +    ) -> Self      {          Self {              commands: commands.into_iter().collect(), +            api_interface_definitions: api_interface_definitions.into_iter().collect(),          }      } -    /// Returns the available commands. +    /// Returns the commands.      #[must_use]      pub fn commands(&self) -> &[Command]      {          &self.commands      } + +    /// Returns the API interface definitions. +    #[must_use] +    pub fn api_interface_definitions(&self) -> &[APIInterfaceDefinition] +    { +        &self.api_interface_definitions +    }  }  impl Deserialize for Registry @@ -127,7 +141,15 @@ impl Deserialize for Registry                  deserializer.de_tag_list::<Command>("command")              })?; -        Ok(Self { commands }) +        deserializer.skip_to_tag_start("feature")?; + +        let api_interface_definitions = +            deserializer.de_tag_list::<APIInterfaceDefinition>("feature")?; + +        Ok(Self { +            commands, +            api_interface_definitions, +        })      }  } @@ -168,3 +190,31 @@ impl From<DeserializerError> for RegistryError  #[derive(Debug, thiserror::Error)]  #[error(transparent)]  pub struct DeserializationError(#[from] DeserializerError); + +#[cfg(test)] +mod tests +{ +    use super::*; + +    #[test] +    fn registry_works() +    { +        let registry = Registry::retrieve().expect("Expected Ok"); + +        for api_interface_def in registry.api_interface_definitions() { +            println!( +                "{} - {}", +                api_interface_def.api_name(), +                api_interface_def.version() +            ); + +            println!("Removals:"); + +            for removal in api_interface_def.removals() { +                for feature in removal.features() { +                    println!("    {:?} - {}", feature.kind(), feature.name()); +                } +            } +        } +    } +} | 
