aboutsummaryrefslogtreecommitdiff
path: root/src/api_interface_definition.rs
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 /src/api_interface_definition.rs
parentc8e5944d697b33738c7ba01694dde5e360470043 (diff)
feat: add deserializing API interface definitions
Diffstat (limited to 'src/api_interface_definition.rs')
-rw-r--r--src/api_interface_definition.rs773
1 files changed, 773 insertions, 0 deletions
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);
+ }
+}