use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TypeKind {
NoType,
Boolean,
Byte,
Int8,
UInt8,
Int16,
UInt16,
Int32,
UInt32,
Int64,
UInt64,
Float32,
Float64,
Float128,
Char8,
Char16,
String8,
String16,
Enumeration,
Bitmask,
Alias,
Array,
Sequence,
Map,
Structure,
Union,
Bitset,
Annotation,
}
impl TypeKind {
#[must_use]
pub const fn is_primitive(self) -> bool {
matches!(
self,
Self::Boolean
| Self::Byte
| Self::Int8
| Self::UInt8
| Self::Int16
| Self::UInt16
| Self::Int32
| Self::UInt32
| Self::Int64
| Self::UInt64
| Self::Float32
| Self::Float64
| Self::Float128
| Self::Char8
| Self::Char16
)
}
#[must_use]
pub const fn is_aggregable(self) -> bool {
matches!(
self,
Self::Structure
| Self::Union
| Self::Annotation
| Self::Bitset
| Self::Bitmask
| Self::Enumeration
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExtensibilityKind {
Final,
Appendable,
Mutable,
}
impl Default for ExtensibilityKind {
fn default() -> Self {
Self::Appendable
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TryConstructKind {
Discard,
UseDefault,
Trim,
}
impl Default for TryConstructKind {
fn default() -> Self {
Self::Discard
}
}
pub type MemberId = u32;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeDescriptor {
pub kind: TypeKind,
pub name: String,
pub base_type: Option<Box<TypeDescriptor>>,
pub discriminator_type: Option<Box<TypeDescriptor>>,
pub bound: Vec<u32>,
pub element_type: Option<Box<TypeDescriptor>>,
pub key_element_type: Option<Box<TypeDescriptor>>,
pub extensibility_kind: ExtensibilityKind,
pub is_nested: bool,
}
impl TypeDescriptor {
#[must_use]
pub fn primitive(kind: TypeKind, name: impl Into<String>) -> Self {
Self {
kind,
name: name.into(),
base_type: None,
discriminator_type: None,
bound: Vec::new(),
element_type: None,
key_element_type: None,
extensibility_kind: ExtensibilityKind::default(),
is_nested: false,
}
}
#[must_use]
pub fn structure(name: impl Into<String>) -> Self {
Self {
kind: TypeKind::Structure,
name: name.into(),
base_type: None,
discriminator_type: None,
bound: Vec::new(),
element_type: None,
key_element_type: None,
extensibility_kind: ExtensibilityKind::default(),
is_nested: false,
}
}
#[must_use]
pub fn union(name: impl Into<String>, discriminator: TypeDescriptor) -> Self {
Self {
kind: TypeKind::Union,
name: name.into(),
base_type: None,
discriminator_type: Some(Box::new(discriminator)),
bound: Vec::new(),
element_type: None,
key_element_type: None,
extensibility_kind: ExtensibilityKind::default(),
is_nested: false,
}
}
#[must_use]
pub fn sequence(name: impl Into<String>, element: TypeDescriptor, max: u32) -> Self {
Self {
kind: TypeKind::Sequence,
name: name.into(),
base_type: None,
discriminator_type: None,
bound: alloc::vec![max],
element_type: Some(Box::new(element)),
key_element_type: None,
extensibility_kind: ExtensibilityKind::default(),
is_nested: false,
}
}
#[must_use]
pub fn array(name: impl Into<String>, element: TypeDescriptor, dims: Vec<u32>) -> Self {
Self {
kind: TypeKind::Array,
name: name.into(),
base_type: None,
discriminator_type: None,
bound: dims,
element_type: Some(Box::new(element)),
key_element_type: None,
extensibility_kind: ExtensibilityKind::default(),
is_nested: false,
}
}
#[must_use]
pub fn map(
name: impl Into<String>,
key: TypeDescriptor,
element: TypeDescriptor,
max: u32,
) -> Self {
Self {
kind: TypeKind::Map,
name: name.into(),
base_type: None,
discriminator_type: None,
bound: alloc::vec![max],
element_type: Some(Box::new(element)),
key_element_type: Some(Box::new(key)),
extensibility_kind: ExtensibilityKind::default(),
is_nested: false,
}
}
#[must_use]
pub fn string8(bound: u32) -> Self {
Self {
kind: TypeKind::String8,
name: alloc::format!("string<{bound}>"),
base_type: None,
discriminator_type: None,
bound: alloc::vec![bound],
element_type: None,
key_element_type: None,
extensibility_kind: ExtensibilityKind::default(),
is_nested: false,
}
}
#[must_use]
pub fn string16(bound: u32) -> Self {
Self {
kind: TypeKind::String16,
name: alloc::format!("wstring<{bound}>"),
base_type: None,
discriminator_type: None,
bound: alloc::vec![bound],
element_type: None,
key_element_type: None,
extensibility_kind: ExtensibilityKind::default(),
is_nested: false,
}
}
#[must_use]
pub fn enumeration(name: impl Into<String>) -> Self {
Self {
kind: TypeKind::Enumeration,
name: name.into(),
base_type: None,
discriminator_type: None,
bound: Vec::new(),
element_type: None,
key_element_type: None,
extensibility_kind: ExtensibilityKind::default(),
is_nested: false,
}
}
pub fn is_consistent(&self) -> Result<(), String> {
if self.name.is_empty() && self.kind != TypeKind::NoType {
return Err(String::from("descriptor without name"));
}
match self.kind {
TypeKind::Union => {
let Some(d) = &self.discriminator_type else {
return Err(String::from("union without discriminator_type"));
};
if !is_valid_discriminator(d.kind) {
return Err(alloc::format!(
"union discriminator must be int/enum/bool, got {:?}",
d.kind
));
}
}
TypeKind::Array => {
if self.bound.is_empty() {
return Err(String::from("array without dimensions"));
}
if self.bound.contains(&0) {
return Err(String::from("array dimension must be > 0"));
}
if self.element_type.is_none() {
return Err(String::from("array without element_type"));
}
}
TypeKind::Sequence | TypeKind::String8 | TypeKind::String16 => {
if self.bound.len() != 1 {
return Err(String::from("sequence/string needs exactly 1 bound"));
}
if matches!(self.kind, TypeKind::Sequence) && self.element_type.is_none() {
return Err(String::from("sequence without element_type"));
}
}
TypeKind::Map => {
if self.bound.len() != 1 {
return Err(String::from("map needs exactly 1 bound"));
}
if self.element_type.is_none() {
return Err(String::from("map without value element_type"));
}
if self.key_element_type.is_none() {
return Err(String::from("map without key_element_type"));
}
}
_ => {}
}
if let Some(b) = &self.base_type {
if b.name == self.name && !self.name.is_empty() {
return Err(String::from("inheritance cycle: base_type == self"));
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MemberDescriptor {
pub name: String,
pub id: MemberId,
pub member_type: Box<TypeDescriptor>,
pub default_value: Option<String>,
pub index: u32,
pub label: Vec<i64>,
pub try_construct: TryConstructKind,
pub is_key: bool,
pub is_optional: bool,
pub is_must_understand: bool,
pub is_shared: bool,
pub is_default_label: bool,
}
impl MemberDescriptor {
#[must_use]
pub fn new(name: impl Into<String>, id: MemberId, ty: TypeDescriptor) -> Self {
Self {
name: name.into(),
id,
member_type: Box::new(ty),
default_value: None,
index: 0,
label: Vec::new(),
try_construct: TryConstructKind::default(),
is_key: false,
is_optional: false,
is_must_understand: false,
is_shared: false,
is_default_label: false,
}
}
pub fn is_consistent(&self) -> Result<(), String> {
if self.name.is_empty() {
return Err(String::from("member without name"));
}
self.member_type.is_consistent()?;
if self.is_default_label && !self.label.is_empty() {
return Err(String::from(
"member with is_default_label must not have explicit labels",
));
}
Ok(())
}
}
const fn is_valid_discriminator(kind: TypeKind) -> bool {
matches!(
kind,
TypeKind::Boolean
| TypeKind::Byte
| TypeKind::Int8
| TypeKind::UInt8
| TypeKind::Int16
| TypeKind::UInt16
| TypeKind::Int32
| TypeKind::UInt32
| TypeKind::Int64
| TypeKind::UInt64
| TypeKind::Char8
| TypeKind::Char16
| TypeKind::Enumeration
)
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn type_kind_primitive_set_matches_spec_table_10() {
for k in [
TypeKind::Boolean,
TypeKind::Byte,
TypeKind::Int8,
TypeKind::UInt8,
TypeKind::Int16,
TypeKind::UInt16,
TypeKind::Int32,
TypeKind::UInt32,
TypeKind::Int64,
TypeKind::UInt64,
TypeKind::Float32,
TypeKind::Float64,
TypeKind::Float128,
TypeKind::Char8,
TypeKind::Char16,
] {
assert!(k.is_primitive(), "{k:?} should be primitive");
}
for k in [
TypeKind::Structure,
TypeKind::Union,
TypeKind::Sequence,
TypeKind::Array,
TypeKind::Map,
TypeKind::String8,
TypeKind::String16,
TypeKind::Alias,
] {
assert!(!k.is_primitive(), "{k:?} should not be primitive");
}
}
#[test]
fn type_kind_aggregable_set() {
assert!(TypeKind::Structure.is_aggregable());
assert!(TypeKind::Union.is_aggregable());
assert!(TypeKind::Annotation.is_aggregable());
assert!(TypeKind::Bitset.is_aggregable());
assert!(TypeKind::Bitmask.is_aggregable());
assert!(TypeKind::Enumeration.is_aggregable());
assert!(!TypeKind::Int32.is_aggregable());
assert!(!TypeKind::Sequence.is_aggregable());
}
#[test]
fn descriptor_struct_passes_consistency() {
let s = TypeDescriptor::structure("::Foo");
assert!(s.is_consistent().is_ok());
}
#[test]
fn descriptor_union_without_discriminator_fails() {
let mut u = TypeDescriptor::structure("::U");
u.kind = TypeKind::Union;
let err = u.is_consistent().unwrap_err();
assert!(err.contains("discriminator"));
}
#[test]
fn descriptor_union_with_invalid_discriminator_fails() {
let bad_disc = TypeDescriptor::structure("::S");
let u = TypeDescriptor::union("::U", bad_disc);
let err = u.is_consistent().unwrap_err();
assert!(err.contains("discriminator"));
}
#[test]
fn descriptor_array_without_dims_fails() {
let mut a = TypeDescriptor::array(
"::A",
TypeDescriptor::primitive(TypeKind::Int32, "int32"),
alloc::vec![3, 3],
);
a.bound.clear();
let err = a.is_consistent().unwrap_err();
assert!(err.contains("dimensions"));
}
#[test]
fn descriptor_array_with_zero_dim_fails() {
let a = TypeDescriptor::array(
"::A",
TypeDescriptor::primitive(TypeKind::Int32, "int32"),
alloc::vec![3, 0, 4],
);
let err = a.is_consistent().unwrap_err();
assert!(err.contains("> 0"));
}
#[test]
fn descriptor_sequence_with_element_passes() {
let s = TypeDescriptor::sequence(
"::S",
TypeDescriptor::primitive(TypeKind::Int32, "int32"),
100,
);
assert!(s.is_consistent().is_ok());
}
#[test]
fn descriptor_map_requires_both_key_and_value() {
let mut m = TypeDescriptor::map(
"::M",
TypeDescriptor::string8(64),
TypeDescriptor::primitive(TypeKind::Int64, "int64"),
500,
);
assert!(m.is_consistent().is_ok());
m.key_element_type = None;
assert!(m.is_consistent().is_err());
}
#[test]
fn descriptor_inheritance_cycle_self_reference_rejected() {
let mut s = TypeDescriptor::structure("::Foo");
let cycle = TypeDescriptor::structure("::Foo");
s.base_type = Some(Box::new(cycle));
let err = s.is_consistent().unwrap_err();
assert!(err.contains("cycle"));
}
#[test]
fn member_descriptor_default_label_with_labels_rejected() {
let mut m =
MemberDescriptor::new("x", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"));
m.is_default_label = true;
m.label = alloc::vec![0];
let err = m.is_consistent().unwrap_err();
assert!(err.contains("default_label"));
}
#[test]
fn member_descriptor_empty_name_rejected() {
let m = MemberDescriptor::new("", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"));
assert!(m.is_consistent().is_err());
}
#[test]
fn try_construct_default_is_discard() {
assert_eq!(TryConstructKind::default(), TryConstructKind::Discard);
}
#[test]
fn extensibility_default_is_appendable() {
assert_eq!(ExtensibilityKind::default(), ExtensibilityKind::Appendable);
}
}