aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2023-03-26 18:50:31 +0200
committerHampusM <hampus@hampusmat.com>2023-03-26 18:50:31 +0200
commit86f41e49f87764f9afd4be1d0d008a320dcfc331 (patch)
tree3e7cf94bcecd42ccf2f1e8eb9928a377d4fdb1bd
parentc8e5944d697b33738c7ba01694dde5e360470043 (diff)
feat: add deserializing API interface definitions
-rw-r--r--Cargo.toml2
-rw-r--r--src/api_interface_definition.rs773
-rw-r--r--src/command.rs35
-rw-r--r--src/deserialization/buffer_deserializer.rs40
-rw-r--r--src/deserialization/mod.rs3
-rw-r--r--src/lib.rs56
6 files changed, 866 insertions, 43 deletions
diff --git a/Cargo.toml b/Cargo.toml
index aa65591..6335e55 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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>;
diff --git a/src/lib.rs b/src/lib.rs
index 68af8ae..4e33f8c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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());
+ }
+ }
+ }
+ }
+}