1use alloc::collections::BTreeMap;
39use alloc::string::{String, ToString};
40use alloc::vec::Vec;
41use core::fmt;
42
43use zerodds_types::builder::{Extensibility as TypeExt, TypeObjectBuilder};
44use zerodds_types::hash::compute_minimal_hash;
45use zerodds_types::type_object::flags::{
46 CollectionElementFlag, CollectionTypeFlag, EnumLiteralFlag, EnumTypeFlag, StructTypeFlag,
47 UnionDiscriminatorFlag, UnionMemberFlag, UnionTypeFlag,
48};
49use zerodds_types::type_object::minimal::{
50 CommonCollectionElement, CommonDiscriminatorMember, CommonEnumeratedHeader,
51 CommonEnumeratedLiteral, MinimalArrayType, MinimalCollectionElement,
52 MinimalDiscriminatorMember, MinimalEnumeratedHeader, MinimalEnumeratedLiteral,
53 MinimalEnumeratedType, MinimalSequenceType, MinimalUnionMember, MinimalUnionType,
54};
55use zerodds_types::{MinimalTypeObject, PrimitiveKind, TypeIdentifier, TypeObject};
56
57use crate::xtypes_def::{
58 BitField, BitValue, BitmaskType, BitsetType, EnumLiteral, EnumType, Extensibility,
59 PrimitiveType as XmlPrimitive, StructMember, StructType, TypeDef, TypeLibrary, TypeRef,
60 TypedefType, UnionDiscriminator, UnionType,
61};
62
63#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum BridgeError {
66 UnsupportedXsdConstruct(String),
69 UnresolvedReference(String),
73 InvalidLiteral(String),
76 HashFailed(String),
79 ModuleAtTopLevel(String),
82}
83
84impl fmt::Display for BridgeError {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 match self {
87 Self::UnsupportedXsdConstruct(s) => {
88 write!(f, "unsupported XSD construct: {s}")
89 }
90 Self::UnresolvedReference(s) => write!(f, "unresolved reference: {s}"),
91 Self::InvalidLiteral(s) => write!(f, "invalid literal: {s}"),
92 Self::HashFailed(s) => write!(f, "TypeIdentifier hashing failed: {s}"),
93 Self::ModuleAtTopLevel(s) => {
94 write!(f, "<module> not allowed at this position: {s}")
95 }
96 }
97 }
98}
99
100#[cfg(feature = "std")]
101impl std::error::Error for BridgeError {}
102
103pub fn xml_type_to_typeobject(xml: &TypeDef) -> Result<TypeObject, BridgeError> {
120 let mto = xml_type_to_minimal_typeobject(xml)?;
121 Ok(TypeObject::Minimal(mto))
122}
123
124pub fn xml_type_to_minimal_typeobject(xml: &TypeDef) -> Result<MinimalTypeObject, BridgeError> {
130 let resolver = NullResolver;
131 bridge_typedef(xml, &resolver)
132}
133
134pub fn bridge_library(
149 lib: &TypeLibrary,
150) -> Result<BTreeMap<String, MinimalTypeObject>, BridgeError> {
151 let flat = flatten(&lib.types, "");
152
153 let pre_resolver = NullResolver;
155 let mut pre: BTreeMap<String, MinimalTypeObject> = BTreeMap::new();
156 let mut pre_hashes: BTreeMap<String, TypeIdentifier> = BTreeMap::new();
157 for (scoped, td) in &flat {
158 let mto = bridge_typedef(td, &pre_resolver)?;
159 let h = compute_minimal_hash(&mto)
160 .map_err(|e| BridgeError::HashFailed(alloc::format!("{e:?}")))?;
161 pre_hashes.insert(scoped.clone(), TypeIdentifier::EquivalenceHashMinimal(h));
162 pre.insert(scoped.clone(), mto);
163 }
164
165 let resolver = MapResolver { named: &pre_hashes };
167 let mut out: BTreeMap<String, MinimalTypeObject> = BTreeMap::new();
168 for (scoped, td) in &flat {
169 let mto = bridge_typedef(td, &resolver)?;
170 out.insert(scoped.clone(), mto);
171 }
172 Ok(out)
173}
174
175trait NameResolver {
183 fn resolve(&self, name: &str) -> TypeIdentifier;
184}
185
186struct NullResolver;
189
190impl NameResolver for NullResolver {
191 fn resolve(&self, _name: &str) -> TypeIdentifier {
192 TypeIdentifier::EquivalenceHashMinimal(zerodds_types::EquivalenceHash([0; 14]))
193 }
194}
195
196struct MapResolver<'a> {
198 named: &'a BTreeMap<String, TypeIdentifier>,
199}
200
201impl NameResolver for MapResolver<'_> {
202 fn resolve(&self, name: &str) -> TypeIdentifier {
203 self.named
204 .get(name)
205 .cloned()
206 .unwrap_or(TypeIdentifier::EquivalenceHashMinimal(
207 zerodds_types::EquivalenceHash([0; 14]),
208 ))
209 }
210}
211
212fn flatten<'a>(types: &'a [TypeDef], prefix: &str) -> Vec<(String, &'a TypeDef)> {
222 let mut out: Vec<(String, &TypeDef)> = Vec::new();
223 for t in types {
224 match t {
225 TypeDef::Module(m) => {
226 let new_prefix = if prefix.is_empty() {
227 m.name.clone()
228 } else {
229 alloc::format!("{prefix}::{}", m.name)
230 };
231 out.extend(flatten(&m.types, &new_prefix));
232 }
233 other => {
234 let scoped = if prefix.is_empty() {
235 other.name().to_string()
236 } else {
237 alloc::format!("{prefix}::{}", other.name())
238 };
239 out.push((scoped, other));
240 }
241 }
242 }
243 out
244}
245
246fn bridge_typedef<R: NameResolver>(
251 xml: &TypeDef,
252 res: &R,
253) -> Result<MinimalTypeObject, BridgeError> {
254 match xml {
255 TypeDef::Struct(s) => bridge_struct(s, res),
256 TypeDef::Enum(e) => Ok(MinimalTypeObject::Enumerated(bridge_enum(e))),
257 TypeDef::Union(u) => bridge_union(u, res),
258 TypeDef::Typedef(t) => bridge_typedef_alias(t, res),
259 TypeDef::Bitmask(b) => Ok(MinimalTypeObject::Bitmask(bridge_bitmask(b))),
260 TypeDef::Bitset(b) => bridge_bitset(b),
261 TypeDef::Module(m) => Err(BridgeError::ModuleAtTopLevel(m.name.clone())),
262 TypeDef::Include(_) | TypeDef::ForwardDcl(_) | TypeDef::Const(_) => {
263 Err(BridgeError::UnsupportedXsdConstruct(alloc::format!(
264 "non-bridgeable XML element kind: {}",
265 xml.name()
266 )))
267 }
268 }
269}
270
271fn bridge_struct<R: NameResolver>(
276 s: &StructType,
277 res: &R,
278) -> Result<MinimalTypeObject, BridgeError> {
279 let ext = map_extensibility(s.extensibility.unwrap_or_default());
280 let mut builder = TypeObjectBuilder::struct_type(s.name.clone()).extensibility(ext);
281
282 if let Some(base) = &s.base_type {
283 builder = builder.base(res.resolve(base));
284 }
285
286 for m in &s.members {
287 let ti = wrap_member_type(m, res)?;
288 let key = m.key;
289 let optional = m.optional;
290 let must_understand = m.must_understand;
291 let explicit_id = m.id;
292 let name = m.name.clone();
293 builder = builder.member(name, ti, |mut mb| {
294 if key {
295 mb = mb.key();
296 }
297 if optional {
298 mb = mb.optional();
299 }
300 if must_understand {
301 mb = mb.must_understand();
302 }
303 if let Some(id) = explicit_id {
304 mb = mb.id(id);
305 }
306 mb
307 });
308 }
309 Ok(MinimalTypeObject::Struct(builder.build_minimal()))
310}
311
312fn map_extensibility(e: Extensibility) -> TypeExt {
313 match e {
314 Extensibility::Final => TypeExt::Final,
315 Extensibility::Appendable => TypeExt::Appendable,
316 Extensibility::Mutable => TypeExt::Mutable,
317 }
318}
319
320fn bridge_enum(e: &EnumType) -> MinimalEnumeratedType {
325 let bit_bound: u16 = e.bit_bound.unwrap_or(32).min(64) as u16;
326 let mut prev: i32 = -1;
329 let literal_seq: Vec<MinimalEnumeratedLiteral> = e
330 .enumerators
331 .iter()
332 .map(|l: &EnumLiteral| {
333 let value = l.value.unwrap_or(prev.saturating_add(1));
334 prev = value;
335 MinimalEnumeratedLiteral {
336 common: CommonEnumeratedLiteral {
337 value,
338 flags: EnumLiteralFlag::default(),
339 },
340 detail: zerodds_types::type_object::common::NameHash::from_name(&l.name),
341 }
342 })
343 .collect();
344 MinimalEnumeratedType {
345 enum_flags: EnumTypeFlag::default(),
346 header: MinimalEnumeratedHeader {
347 common: CommonEnumeratedHeader { bit_bound },
348 },
349 literal_seq,
350 }
351}
352
353fn bridge_union<R: NameResolver>(u: &UnionType, res: &R) -> Result<MinimalTypeObject, BridgeError> {
358 let disc_ti = type_ref_to_identifier(&u.discriminator, res);
359 let mut next_seq: u32 = 1;
360 let mut member_seq: Vec<MinimalUnionMember> = Vec::with_capacity(u.cases.len());
361 for case in &u.cases {
362 let m = &case.member;
363 let mut labels: Vec<i32> = Vec::with_capacity(case.discriminators.len());
364 let mut is_default = false;
365 for d in &case.discriminators {
366 match d {
367 UnionDiscriminator::Default => {
368 is_default = true;
369 }
370 UnionDiscriminator::Value(s) => {
371 let v = parse_label(s)?;
372 labels.push(v);
373 }
374 }
375 }
376 let id = m.id.unwrap_or_else(|| {
377 let v = next_seq;
378 next_seq += 1;
379 v
380 });
381 let ti = wrap_member_type(m, res)?;
382 let mut flags: u16 = 0;
383 if is_default {
384 flags |= UnionMemberFlag::IS_DEFAULT;
385 }
386 member_seq.push(MinimalUnionMember {
387 common: zerodds_types::type_object::common::CommonUnionMember {
388 member_id: id,
389 member_flags: UnionMemberFlag(flags),
390 type_id: ti,
391 label_seq: labels,
392 },
393 detail: zerodds_types::type_object::common::NameHash::from_name(&m.name),
394 });
395 }
396 let _ = &u.name;
400 let union_flags_bits = StructTypeFlag::IS_APPENDABLE;
401 Ok(MinimalTypeObject::Union(MinimalUnionType {
402 union_flags: UnionTypeFlag(union_flags_bits),
403 discriminator: MinimalDiscriminatorMember {
404 common: CommonDiscriminatorMember {
405 member_flags: UnionDiscriminatorFlag::default(),
406 type_id: disc_ti,
407 },
408 },
409 member_seq,
410 }))
411}
412
413fn parse_label(s: &str) -> Result<i32, BridgeError> {
414 let trimmed = s.trim();
415 if let Some(rest) = trimmed
416 .strip_prefix("0x")
417 .or_else(|| trimmed.strip_prefix("0X"))
418 {
419 return i64::from_str_radix(rest, 16)
420 .ok()
421 .and_then(|v| i32::try_from(v).ok())
422 .ok_or_else(|| BridgeError::InvalidLiteral(s.to_string()));
423 }
424 trimmed
425 .parse::<i32>()
426 .map_err(|_| BridgeError::InvalidLiteral(s.to_string()))
427}
428
429fn bridge_typedef_alias<R: NameResolver>(
434 t: &TypedefType,
435 res: &R,
436) -> Result<MinimalTypeObject, BridgeError> {
437 if !t.array_dimensions.is_empty() {
441 let element =
442 type_ref_to_identifier_with_string_bound(&t.type_ref, t.string_max_length, res);
443 return Ok(MinimalTypeObject::Array(MinimalArrayType {
444 collection_flag: CollectionTypeFlag::default(),
445 bound_seq: t.array_dimensions.clone(),
446 element: MinimalCollectionElement {
447 common: CommonCollectionElement {
448 element_flags: CollectionElementFlag::default(),
449 type_id: element,
450 },
451 },
452 }));
453 }
454 if let Some(bound) = t.sequence_max_length {
455 let element =
456 type_ref_to_identifier_with_string_bound(&t.type_ref, t.string_max_length, res);
457 return Ok(MinimalTypeObject::Sequence(MinimalSequenceType {
458 collection_flag: CollectionTypeFlag::default(),
459 bound,
460 element: MinimalCollectionElement {
461 common: CommonCollectionElement {
462 element_flags: CollectionElementFlag::default(),
463 type_id: element,
464 },
465 },
466 }));
467 }
468 let related = type_ref_to_identifier_with_string_bound(&t.type_ref, t.string_max_length, res);
469 let alias = TypeObjectBuilder::alias(t.name.clone(), related).build_minimal();
470 Ok(MinimalTypeObject::Alias(alias))
471}
472
473fn bridge_bitmask(b: &BitmaskType) -> zerodds_types::type_object::minimal::MinimalBitmaskType {
478 let bit_bound: u16 = b.bit_bound.unwrap_or(32).min(64) as u16;
479 let mut prev: i32 = -1;
480 let mut builder = TypeObjectBuilder::bitmask(b.name.clone()).bit_bound(bit_bound);
481 for v in &b.bit_values {
482 let pos = match v.position {
483 Some(p) => {
484 prev = p as i32;
485 p
486 }
487 None => {
488 let p = (prev.saturating_add(1)).max(0) as u32;
489 prev = p as i32;
490 p
491 }
492 };
493 let p_u16 = pos as u16;
494 let _ = v as &BitValue;
495 builder = builder.flag(v.name.clone(), p_u16);
496 }
497 builder.build_minimal()
498}
499
500fn bridge_bitset(b: &BitsetType) -> Result<MinimalTypeObject, BridgeError> {
505 use zerodds_types::type_identifier::kinds::{
506 TK_INT8, TK_INT16, TK_INT32, TK_INT64, TK_UINT8, TK_UINT16, TK_UINT32, TK_UINT64,
507 };
508 let mut next_pos: u16 = 0;
510 let mut builder = TypeObjectBuilder::bitset(b.name.clone());
511 for f in &b.bit_fields {
512 let bitcount = parse_bitset_mask_bits(&f.mask)?;
513 let holder = match &f.type_ref {
514 TypeRef::Primitive(p) => holder_kind_byte(*p).ok_or_else(|| {
515 BridgeError::UnsupportedXsdConstruct(alloc::format!(
516 "bitset holder type: {}",
517 p.as_xml()
518 ))
519 })?,
520 TypeRef::Named(_) => {
521 return Err(BridgeError::UnsupportedXsdConstruct(
522 "bitset holder must be a primitive integer".to_string(),
523 ));
524 }
525 };
526 debug_assert!(matches!(
528 holder,
529 TK_INT8 | TK_INT16 | TK_INT32 | TK_INT64 | TK_UINT8 | TK_UINT16 | TK_UINT32 | TK_UINT64
530 ));
531 let pos = next_pos;
532 next_pos = next_pos.saturating_add(u16::from(bitcount));
533 builder = builder.field(f.name.clone(), pos, bitcount, holder);
534 let _ = f as &BitField;
535 }
536 Ok(MinimalTypeObject::Bitset(builder.build_minimal()))
537}
538
539fn holder_kind_byte(p: XmlPrimitive) -> Option<u8> {
540 use zerodds_types::type_identifier::kinds::*;
541 Some(match p {
542 XmlPrimitive::Octet => TK_BYTE,
543 XmlPrimitive::Short => TK_INT16,
544 XmlPrimitive::UShort => TK_UINT16,
545 XmlPrimitive::Long => TK_INT32,
546 XmlPrimitive::ULong => TK_UINT32,
547 XmlPrimitive::LongLong => TK_INT64,
548 XmlPrimitive::ULongLong => TK_UINT64,
549 XmlPrimitive::Boolean => TK_BOOLEAN,
550 XmlPrimitive::Char => TK_CHAR8,
551 XmlPrimitive::WChar => TK_CHAR16,
552 _ => return None,
553 })
554}
555
556fn parse_bitset_mask_bits(mask: &str) -> Result<u8, BridgeError> {
558 let trimmed = mask.trim();
559 let (rest, radix) = if let Some(r) = trimmed
560 .strip_prefix("0x")
561 .or_else(|| trimmed.strip_prefix("0X"))
562 {
563 (r, 16u32)
564 } else if let Some(r) = trimmed
565 .strip_prefix("0b")
566 .or_else(|| trimmed.strip_prefix("0B"))
567 {
568 (r, 2u32)
569 } else {
570 (trimmed, 10u32)
571 };
572 let v = u64::from_str_radix(rest, radix)
573 .map_err(|_| BridgeError::InvalidLiteral(alloc::format!("bitset mask {mask}")))?;
574 let bits = u8::try_from(v.count_ones())
575 .map_err(|_| BridgeError::InvalidLiteral(alloc::format!("bitset mask too wide: {mask}")))?;
576 if bits == 0 {
577 return Err(BridgeError::InvalidLiteral(alloc::format!(
578 "bitset mask must have >= 1 bit: {mask}"
579 )));
580 }
581 Ok(bits)
582}
583
584fn wrap_member_type<R: NameResolver>(
591 m: &StructMember,
592 res: &R,
593) -> Result<TypeIdentifier, BridgeError> {
594 let inner = type_ref_to_identifier_with_string_bound(&m.type_ref, m.string_max_length, res);
595
596 if !m.array_dimensions.is_empty() {
598 return Ok(wrap_array(inner, &m.array_dimensions));
599 }
600 if let Some(bound) = m.sequence_max_length {
601 return Ok(wrap_sequence(inner, bound));
602 }
603 Ok(inner)
604}
605
606fn wrap_sequence(element: TypeIdentifier, bound: u32) -> TypeIdentifier {
607 if bound <= 255 {
608 TypeIdentifier::PlainSequenceSmall {
609 header: zerodds_types::PlainCollectionHeader::default(),
610 bound: bound as u8,
611 element: alloc::boxed::Box::new(element),
612 }
613 } else {
614 TypeIdentifier::PlainSequenceLarge {
615 header: zerodds_types::PlainCollectionHeader::default(),
616 bound,
617 element: alloc::boxed::Box::new(element),
618 }
619 }
620}
621
622fn wrap_array(element: TypeIdentifier, dims: &[u32]) -> TypeIdentifier {
623 let any_large = dims.iter().any(|d| *d > 255);
624 if any_large {
625 TypeIdentifier::PlainArrayLarge {
626 header: zerodds_types::PlainCollectionHeader::default(),
627 array_bounds: dims.to_vec(),
628 element: alloc::boxed::Box::new(element),
629 }
630 } else {
631 TypeIdentifier::PlainArraySmall {
632 header: zerodds_types::PlainCollectionHeader::default(),
633 array_bounds: dims.iter().map(|d| *d as u8).collect(),
634 element: alloc::boxed::Box::new(element),
635 }
636 }
637}
638
639fn type_ref_to_identifier<R: NameResolver>(t: &TypeRef, res: &R) -> TypeIdentifier {
640 type_ref_to_identifier_with_string_bound(t, None, res)
641}
642
643fn type_ref_to_identifier_with_string_bound<R: NameResolver>(
644 t: &TypeRef,
645 string_max_length: Option<u32>,
646 res: &R,
647) -> TypeIdentifier {
648 match t {
649 TypeRef::Primitive(p) => primitive_to_identifier(*p, string_max_length),
650 TypeRef::Named(n) => res.resolve(n),
651 }
652}
653
654fn primitive_to_identifier(p: XmlPrimitive, bound: Option<u32>) -> TypeIdentifier {
655 match p {
656 XmlPrimitive::Boolean => TypeIdentifier::Primitive(PrimitiveKind::Boolean),
657 XmlPrimitive::Octet => TypeIdentifier::Primitive(PrimitiveKind::Byte),
658 XmlPrimitive::Char => TypeIdentifier::Primitive(PrimitiveKind::Char8),
659 XmlPrimitive::WChar => TypeIdentifier::Primitive(PrimitiveKind::Char16),
660 XmlPrimitive::Short => TypeIdentifier::Primitive(PrimitiveKind::Int16),
661 XmlPrimitive::UShort => TypeIdentifier::Primitive(PrimitiveKind::UInt16),
662 XmlPrimitive::Long => TypeIdentifier::Primitive(PrimitiveKind::Int32),
663 XmlPrimitive::ULong => TypeIdentifier::Primitive(PrimitiveKind::UInt32),
664 XmlPrimitive::LongLong => TypeIdentifier::Primitive(PrimitiveKind::Int64),
665 XmlPrimitive::ULongLong => TypeIdentifier::Primitive(PrimitiveKind::UInt64),
666 XmlPrimitive::Float => TypeIdentifier::Primitive(PrimitiveKind::Float32),
667 XmlPrimitive::Double => TypeIdentifier::Primitive(PrimitiveKind::Float64),
668 XmlPrimitive::LongDouble => TypeIdentifier::Primitive(PrimitiveKind::Float128),
669 XmlPrimitive::String => string_id(false, bound.unwrap_or(0)),
670 XmlPrimitive::WString => string_id(true, bound.unwrap_or(0)),
671 }
672}
673
674fn string_id(wide: bool, bound: u32) -> TypeIdentifier {
675 if wide {
676 if bound <= 255 {
677 TypeIdentifier::String16Small { bound: bound as u8 }
678 } else {
679 TypeIdentifier::String16Large { bound }
680 }
681 } else if bound <= 255 {
682 TypeIdentifier::String8Small { bound: bound as u8 }
683 } else {
684 TypeIdentifier::String8Large { bound }
685 }
686}
687
688#[cfg(test)]
693#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
694mod tests {
695 use super::*;
696 use crate::xtypes_def::{
697 BitField, BitValue, BitmaskType, BitsetType, EnumLiteral, EnumType, ModuleEntry,
698 StructMember, StructType, TypedefType, UnionCase, UnionType,
699 };
700
701 fn primitive(p: XmlPrimitive) -> TypeRef {
702 TypeRef::Primitive(p)
703 }
704
705 fn make_struct() -> StructType {
706 StructType {
707 name: "Position".into(),
708 extensibility: Some(Extensibility::Final),
709 base_type: None,
710 members: alloc::vec![
711 StructMember {
712 name: "x".into(),
713 type_ref: primitive(XmlPrimitive::Float),
714 key: true,
715 ..Default::default()
716 },
717 StructMember {
718 name: "y".into(),
719 type_ref: primitive(XmlPrimitive::Float),
720 ..Default::default()
721 },
722 ],
723 }
724 }
725
726 #[test]
729 fn struct_two_members_lower_to_minimal_struct() {
730 let s = make_struct();
731 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
732 match mto {
733 MinimalTypeObject::Struct(st) => {
734 assert_eq!(st.member_seq.len(), 2);
735 assert!(st.struct_flags.has(StructTypeFlag::IS_FINAL));
736 assert!(matches!(
737 st.member_seq[0].common.member_type_id,
738 TypeIdentifier::Primitive(PrimitiveKind::Float32)
739 ));
740 }
741 other => panic!("expected struct, got {other:?}"),
742 }
743 }
744
745 #[test]
746 fn struct_explicit_id_is_preserved() {
747 let s = StructType {
748 name: "X".into(),
749 extensibility: None,
750 base_type: None,
751 members: alloc::vec![StructMember {
752 name: "a".into(),
753 type_ref: primitive(XmlPrimitive::Long),
754 id: Some(42),
755 ..Default::default()
756 }],
757 };
758 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
759 if let MinimalTypeObject::Struct(st) = mto {
760 assert_eq!(st.member_seq[0].common.member_id, 42);
761 } else {
762 panic!("not a struct");
763 }
764 }
765
766 #[test]
767 fn struct_autoid_sequential_starts_at_0() {
768 let s = StructType {
769 name: "X".into(),
770 extensibility: None,
771 base_type: None,
772 members: alloc::vec![
773 StructMember {
774 name: "a".into(),
775 type_ref: primitive(XmlPrimitive::Long),
776 ..Default::default()
777 },
778 StructMember {
779 name: "b".into(),
780 type_ref: primitive(XmlPrimitive::Long),
781 ..Default::default()
782 },
783 ],
784 };
785 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
786 if let MinimalTypeObject::Struct(st) = mto {
787 assert_eq!(st.member_seq[0].common.member_id, 0);
789 assert_eq!(st.member_seq[1].common.member_id, 1);
790 } else {
791 panic!("not a struct");
792 }
793 }
794
795 #[test]
796 fn struct_member_flags_key_optional_must_understand() {
797 use zerodds_types::type_object::flags::StructMemberFlag;
798 let s = StructType {
799 name: "X".into(),
800 extensibility: None,
801 base_type: None,
802 members: alloc::vec![StructMember {
803 name: "a".into(),
804 type_ref: primitive(XmlPrimitive::Long),
805 key: true,
806 optional: true,
807 must_understand: true,
808 ..Default::default()
809 }],
810 };
811 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
812 if let MinimalTypeObject::Struct(st) = mto {
813 let f = st.member_seq[0].common.member_flags;
814 assert!(f.has(StructMemberFlag::IS_KEY));
815 assert!(f.has(StructMemberFlag::IS_OPTIONAL));
816 assert!(f.has(StructMemberFlag::IS_MUST_UNDERSTAND));
817 } else {
818 panic!("not a struct");
819 }
820 }
821
822 #[test]
823 fn struct_string_member_with_bound_uses_string_small() {
824 let s = StructType {
825 name: "X".into(),
826 extensibility: None,
827 base_type: None,
828 members: alloc::vec![StructMember {
829 name: "name".into(),
830 type_ref: primitive(XmlPrimitive::String),
831 string_max_length: Some(64),
832 ..Default::default()
833 }],
834 };
835 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
836 if let MinimalTypeObject::Struct(st) = mto {
837 assert_eq!(
838 st.member_seq[0].common.member_type_id,
839 TypeIdentifier::String8Small { bound: 64 }
840 );
841 } else {
842 panic!();
843 }
844 }
845
846 #[test]
847 fn struct_member_with_array_dimensions_wraps_into_plain_array() {
848 let s = StructType {
849 name: "X".into(),
850 extensibility: None,
851 base_type: None,
852 members: alloc::vec![StructMember {
853 name: "a".into(),
854 type_ref: primitive(XmlPrimitive::Long),
855 array_dimensions: alloc::vec![3, 4],
856 ..Default::default()
857 }],
858 };
859 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
860 if let MinimalTypeObject::Struct(st) = mto {
861 assert!(matches!(
862 st.member_seq[0].common.member_type_id,
863 TypeIdentifier::PlainArraySmall { .. }
864 ));
865 } else {
866 panic!();
867 }
868 }
869
870 #[test]
871 fn struct_member_with_sequence_bound_wraps_into_plain_sequence() {
872 let s = StructType {
873 name: "X".into(),
874 extensibility: None,
875 base_type: None,
876 members: alloc::vec![StructMember {
877 name: "a".into(),
878 type_ref: primitive(XmlPrimitive::Long),
879 sequence_max_length: Some(100),
880 ..Default::default()
881 }],
882 };
883 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
884 if let MinimalTypeObject::Struct(st) = mto {
885 assert!(matches!(
886 st.member_seq[0].common.member_type_id,
887 TypeIdentifier::PlainSequenceSmall { bound: 100, .. }
888 ));
889 } else {
890 panic!();
891 }
892 }
893
894 #[test]
895 fn struct_member_with_large_array_uses_plain_array_large() {
896 let s = StructType {
897 name: "X".into(),
898 extensibility: None,
899 base_type: None,
900 members: alloc::vec![StructMember {
901 name: "a".into(),
902 type_ref: primitive(XmlPrimitive::Long),
903 array_dimensions: alloc::vec![300, 4],
904 ..Default::default()
905 }],
906 };
907 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
908 if let MinimalTypeObject::Struct(st) = mto {
909 assert!(matches!(
910 st.member_seq[0].common.member_type_id,
911 TypeIdentifier::PlainArrayLarge { .. }
912 ));
913 } else {
914 panic!();
915 }
916 }
917
918 #[test]
919 fn struct_extensibility_mutable_sets_flag() {
920 let mut s = make_struct();
921 s.extensibility = Some(Extensibility::Mutable);
922 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
923 if let MinimalTypeObject::Struct(st) = mto {
924 assert!(st.struct_flags.has(StructTypeFlag::IS_MUTABLE));
925 } else {
926 panic!();
927 }
928 }
929
930 #[test]
933 fn enum_with_explicit_values() {
934 let e = EnumType {
935 name: "Color".into(),
936 bit_bound: Some(16),
937 enumerators: alloc::vec![
938 EnumLiteral {
939 name: "RED".into(),
940 value: Some(0),
941 },
942 EnumLiteral {
943 name: "GREEN".into(),
944 value: Some(1),
945 },
946 EnumLiteral {
947 name: "BLUE".into(),
948 value: Some(2),
949 },
950 ],
951 };
952 let mto = xml_type_to_minimal_typeobject(&TypeDef::Enum(e)).unwrap();
953 if let MinimalTypeObject::Enumerated(en) = mto {
954 assert_eq!(en.header.common.bit_bound, 16);
955 assert_eq!(en.literal_seq.len(), 3);
956 assert_eq!(en.literal_seq[2].common.value, 2);
957 } else {
958 panic!();
959 }
960 }
961
962 #[test]
963 fn enum_auto_numbering_starts_at_zero() {
964 let e = EnumType {
965 name: "X".into(),
966 bit_bound: None,
967 enumerators: alloc::vec![
968 EnumLiteral {
969 name: "A".into(),
970 value: None,
971 },
972 EnumLiteral {
973 name: "B".into(),
974 value: None,
975 },
976 EnumLiteral {
977 name: "C".into(),
978 value: Some(10),
979 },
980 EnumLiteral {
981 name: "D".into(),
982 value: None,
983 },
984 ],
985 };
986 let mto = xml_type_to_minimal_typeobject(&TypeDef::Enum(e)).unwrap();
987 if let MinimalTypeObject::Enumerated(en) = mto {
988 assert_eq!(en.literal_seq[0].common.value, 0);
989 assert_eq!(en.literal_seq[1].common.value, 1);
990 assert_eq!(en.literal_seq[2].common.value, 10);
991 assert_eq!(en.literal_seq[3].common.value, 11);
992 assert_eq!(en.header.common.bit_bound, 32);
994 } else {
995 panic!();
996 }
997 }
998
999 #[test]
1002 fn union_with_int_disc_and_default_branch() {
1003 let u = UnionType {
1004 name: "U".into(),
1005 discriminator: primitive(XmlPrimitive::Long),
1006 cases: alloc::vec![
1007 UnionCase {
1008 discriminators: alloc::vec![
1009 UnionDiscriminator::Value("1".into()),
1010 UnionDiscriminator::Value("2".into()),
1011 ],
1012 member: StructMember {
1013 name: "a".into(),
1014 type_ref: primitive(XmlPrimitive::LongLong),
1015 ..Default::default()
1016 },
1017 },
1018 UnionCase {
1019 discriminators: alloc::vec![UnionDiscriminator::Default],
1020 member: StructMember {
1021 name: "b".into(),
1022 type_ref: primitive(XmlPrimitive::String),
1023 string_max_length: Some(64),
1024 ..Default::default()
1025 },
1026 },
1027 ],
1028 };
1029 let mto = xml_type_to_minimal_typeobject(&TypeDef::Union(u)).unwrap();
1030 if let MinimalTypeObject::Union(un) = mto {
1031 assert_eq!(un.member_seq.len(), 2);
1032 assert_eq!(un.member_seq[0].common.label_seq, alloc::vec![1_i32, 2]);
1033 assert!(un.member_seq[1].common.member_flags.0 & UnionMemberFlag::IS_DEFAULT != 0);
1034 } else {
1035 panic!();
1036 }
1037 }
1038
1039 #[test]
1040 fn union_label_hex_parsed() {
1041 let u = UnionType {
1042 name: "U".into(),
1043 discriminator: primitive(XmlPrimitive::Long),
1044 cases: alloc::vec![UnionCase {
1045 discriminators: alloc::vec![UnionDiscriminator::Value("0x10".into())],
1046 member: StructMember {
1047 name: "a".into(),
1048 type_ref: primitive(XmlPrimitive::Long),
1049 ..Default::default()
1050 },
1051 }],
1052 };
1053 let mto = xml_type_to_minimal_typeobject(&TypeDef::Union(u)).unwrap();
1054 if let MinimalTypeObject::Union(un) = mto {
1055 assert_eq!(un.member_seq[0].common.label_seq, alloc::vec![16_i32]);
1056 } else {
1057 panic!();
1058 }
1059 }
1060
1061 #[test]
1062 fn union_label_invalid_returns_invalid_literal() {
1063 let u = UnionType {
1064 name: "U".into(),
1065 discriminator: primitive(XmlPrimitive::Long),
1066 cases: alloc::vec![UnionCase {
1067 discriminators: alloc::vec![UnionDiscriminator::Value("not-an-int".into())],
1068 member: StructMember {
1069 name: "a".into(),
1070 type_ref: primitive(XmlPrimitive::Long),
1071 ..Default::default()
1072 },
1073 }],
1074 };
1075 let err = xml_type_to_minimal_typeobject(&TypeDef::Union(u)).unwrap_err();
1076 assert!(matches!(err, BridgeError::InvalidLiteral(_)));
1077 }
1078
1079 #[test]
1082 fn typedef_to_alias() {
1083 let t = TypedefType {
1084 name: "Count".into(),
1085 type_ref: primitive(XmlPrimitive::ULongLong),
1086 ..Default::default()
1087 };
1088 let mto = xml_type_to_minimal_typeobject(&TypeDef::Typedef(t)).unwrap();
1089 if let MinimalTypeObject::Alias(a) = mto {
1090 assert_eq!(
1091 a.body.common.related_type,
1092 TypeIdentifier::Primitive(PrimitiveKind::UInt64)
1093 );
1094 } else {
1095 panic!("not an alias");
1096 }
1097 }
1098
1099 #[test]
1100 fn typedef_with_array_dims_lowers_to_array_type() {
1101 let t = TypedefType {
1102 name: "Vec3".into(),
1103 type_ref: primitive(XmlPrimitive::Float),
1104 array_dimensions: alloc::vec![3],
1105 ..Default::default()
1106 };
1107 let mto = xml_type_to_minimal_typeobject(&TypeDef::Typedef(t)).unwrap();
1108 match mto {
1109 MinimalTypeObject::Array(a) => {
1110 assert_eq!(a.bound_seq, alloc::vec![3]);
1111 assert_eq!(
1112 a.element.common.type_id,
1113 TypeIdentifier::Primitive(PrimitiveKind::Float32)
1114 );
1115 }
1116 other => panic!("expected array, got {other:?}"),
1117 }
1118 }
1119
1120 #[test]
1121 fn typedef_with_sequence_bound_lowers_to_sequence_type() {
1122 let t = TypedefType {
1123 name: "Bytes".into(),
1124 type_ref: primitive(XmlPrimitive::Octet),
1125 sequence_max_length: Some(1024),
1126 ..Default::default()
1127 };
1128 let mto = xml_type_to_minimal_typeobject(&TypeDef::Typedef(t)).unwrap();
1129 match mto {
1130 MinimalTypeObject::Sequence(s) => {
1131 assert_eq!(s.bound, 1024);
1132 }
1133 _ => panic!(),
1134 }
1135 }
1136
1137 #[test]
1140 fn bitmask_three_flags() {
1141 let b = BitmaskType {
1142 name: "Perms".into(),
1143 bit_bound: Some(8),
1144 bit_values: alloc::vec![
1145 BitValue {
1146 name: "READ".into(),
1147 position: Some(0),
1148 },
1149 BitValue {
1150 name: "WRITE".into(),
1151 position: Some(1),
1152 },
1153 BitValue {
1154 name: "EXEC".into(),
1155 position: Some(2),
1156 },
1157 ],
1158 };
1159 let mto = xml_type_to_minimal_typeobject(&TypeDef::Bitmask(b)).unwrap();
1160 if let MinimalTypeObject::Bitmask(bm) = mto {
1161 assert_eq!(bm.bit_bound, 8);
1162 assert_eq!(bm.flag_seq.len(), 3);
1163 assert_eq!(bm.flag_seq[0].common.position, 0);
1164 assert_eq!(bm.flag_seq[2].common.position, 2);
1165 } else {
1166 panic!();
1167 }
1168 }
1169
1170 #[test]
1171 fn bitmask_auto_position() {
1172 let b = BitmaskType {
1173 name: "X".into(),
1174 bit_bound: None,
1175 bit_values: alloc::vec![
1176 BitValue {
1177 name: "A".into(),
1178 position: None,
1179 },
1180 BitValue {
1181 name: "B".into(),
1182 position: None,
1183 },
1184 BitValue {
1185 name: "C".into(),
1186 position: Some(10),
1187 },
1188 BitValue {
1189 name: "D".into(),
1190 position: None,
1191 },
1192 ],
1193 };
1194 let mto = xml_type_to_minimal_typeobject(&TypeDef::Bitmask(b)).unwrap();
1195 if let MinimalTypeObject::Bitmask(bm) = mto {
1196 assert_eq!(bm.flag_seq[0].common.position, 0);
1197 assert_eq!(bm.flag_seq[1].common.position, 1);
1198 assert_eq!(bm.flag_seq[2].common.position, 10);
1199 assert_eq!(bm.flag_seq[3].common.position, 11);
1200 assert_eq!(bm.bit_bound, 32);
1201 } else {
1202 panic!();
1203 }
1204 }
1205
1206 #[test]
1209 fn bitset_with_two_fields() {
1210 let b = BitsetType {
1211 name: "Packed".into(),
1212 bit_fields: alloc::vec![
1213 BitField {
1214 name: "header".into(),
1215 type_ref: primitive(XmlPrimitive::ULong),
1216 mask: "0x0F".into(),
1217 },
1218 BitField {
1219 name: "body".into(),
1220 type_ref: primitive(XmlPrimitive::ULong),
1221 mask: "0xFFFFFFF0".into(),
1222 },
1223 ],
1224 };
1225 let mto = xml_type_to_minimal_typeobject(&TypeDef::Bitset(b)).unwrap();
1226 if let MinimalTypeObject::Bitset(bs) = mto {
1227 assert_eq!(bs.field_seq.len(), 2);
1228 assert_eq!(bs.field_seq[0].common.bitcount, 4);
1229 assert_eq!(bs.field_seq[0].common.position, 0);
1230 assert_eq!(bs.field_seq[1].common.bitcount, 28);
1231 assert_eq!(bs.field_seq[1].common.position, 4);
1232 } else {
1233 panic!();
1234 }
1235 }
1236
1237 #[test]
1238 fn bitset_invalid_mask_rejected() {
1239 let b = BitsetType {
1240 name: "X".into(),
1241 bit_fields: alloc::vec![BitField {
1242 name: "f".into(),
1243 type_ref: primitive(XmlPrimitive::ULong),
1244 mask: "garbage".into(),
1245 }],
1246 };
1247 let err = xml_type_to_minimal_typeobject(&TypeDef::Bitset(b)).unwrap_err();
1248 assert!(matches!(err, BridgeError::InvalidLiteral(_)));
1249 }
1250
1251 #[test]
1252 fn bitset_zero_mask_rejected() {
1253 let b = BitsetType {
1254 name: "X".into(),
1255 bit_fields: alloc::vec![BitField {
1256 name: "f".into(),
1257 type_ref: primitive(XmlPrimitive::ULong),
1258 mask: "0x00".into(),
1259 }],
1260 };
1261 let err = xml_type_to_minimal_typeobject(&TypeDef::Bitset(b)).unwrap_err();
1262 assert!(matches!(err, BridgeError::InvalidLiteral(_)));
1263 }
1264
1265 #[test]
1268 fn module_at_top_level_is_error() {
1269 let m = TypeDef::Module(ModuleEntry {
1270 name: "M".into(),
1271 types: alloc::vec![],
1272 });
1273 let err = xml_type_to_minimal_typeobject(&m).unwrap_err();
1274 assert!(matches!(err, BridgeError::ModuleAtTopLevel(_)));
1275 }
1276
1277 #[test]
1278 fn library_resolves_named_refs_between_types() {
1279 let lib = TypeLibrary {
1282 name: "Lib".into(),
1283 types: alloc::vec![
1284 TypeDef::Struct(StructType {
1285 name: "Point".into(),
1286 extensibility: None,
1287 base_type: None,
1288 members: alloc::vec![
1289 StructMember {
1290 name: "x".into(),
1291 type_ref: primitive(XmlPrimitive::Float),
1292 ..Default::default()
1293 },
1294 StructMember {
1295 name: "y".into(),
1296 type_ref: primitive(XmlPrimitive::Float),
1297 ..Default::default()
1298 },
1299 ],
1300 }),
1301 TypeDef::Struct(StructType {
1302 name: "Line".into(),
1303 extensibility: None,
1304 base_type: None,
1305 members: alloc::vec![
1306 StructMember {
1307 name: "a".into(),
1308 type_ref: TypeRef::Named("Point".into()),
1309 ..Default::default()
1310 },
1311 StructMember {
1312 name: "b".into(),
1313 type_ref: TypeRef::Named("Point".into()),
1314 ..Default::default()
1315 },
1316 ],
1317 }),
1318 ],
1319 };
1320 let map = bridge_library(&lib).unwrap();
1321 let line = map.get("Line").expect("Line registered");
1322 if let MinimalTypeObject::Struct(st) = line {
1323 assert_eq!(
1325 st.member_seq[0].common.member_type_id,
1326 st.member_seq[1].common.member_type_id
1327 );
1328 assert!(matches!(
1329 st.member_seq[0].common.member_type_id,
1330 TypeIdentifier::EquivalenceHashMinimal(_)
1331 ));
1332 } else {
1333 panic!("Line not a struct");
1334 }
1335 assert!(map.contains_key("Point"));
1337 }
1338
1339 #[test]
1340 fn library_module_flattens_to_scoped_name() {
1341 let lib = TypeLibrary {
1342 name: "L".into(),
1343 types: alloc::vec![TypeDef::Module(ModuleEntry {
1344 name: "Inner".into(),
1345 types: alloc::vec![TypeDef::Struct(StructType {
1346 name: "S".into(),
1347 extensibility: None,
1348 base_type: None,
1349 members: alloc::vec![],
1350 })],
1351 })],
1352 };
1353 let map = bridge_library(&lib).unwrap();
1354 assert!(map.contains_key("Inner::S"));
1355 }
1356
1357 #[test]
1358 fn library_unknown_named_ref_falls_back_to_zero_hash() {
1359 let lib = TypeLibrary {
1360 name: "L".into(),
1361 types: alloc::vec![TypeDef::Struct(StructType {
1362 name: "S".into(),
1363 extensibility: None,
1364 base_type: None,
1365 members: alloc::vec![StructMember {
1366 name: "missing".into(),
1367 type_ref: TypeRef::Named("DoesNotExist".into()),
1368 ..Default::default()
1369 }],
1370 })],
1371 };
1372 let map = bridge_library(&lib).unwrap();
1373 let s = map.get("S").unwrap();
1374 if let MinimalTypeObject::Struct(st) = s {
1375 assert_eq!(
1377 st.member_seq[0].common.member_type_id,
1378 TypeIdentifier::EquivalenceHashMinimal(zerodds_types::EquivalenceHash([0; 14]))
1379 );
1380 } else {
1381 panic!();
1382 }
1383 }
1384
1385 #[test]
1388 fn typeobject_can_be_hashed() {
1389 let s = make_struct();
1390 let to = xml_type_to_typeobject(&TypeDef::Struct(s)).unwrap();
1391 let h = zerodds_types::compute_hash(&to).expect("hash");
1392 let h2 = zerodds_types::compute_hash(&to).unwrap();
1394 assert_eq!(h, h2);
1395 }
1396
1397 #[test]
1398 fn xml_struct_matches_idl_struct_hash() {
1399 let xml_struct = StructType {
1403 name: "Sensor".into(),
1404 extensibility: Some(Extensibility::Appendable),
1405 base_type: None,
1406 members: alloc::vec![
1407 StructMember {
1408 name: "id".into(),
1409 type_ref: primitive(XmlPrimitive::LongLong),
1410 key: true,
1411 ..Default::default()
1412 },
1413 StructMember {
1414 name: "temp".into(),
1415 type_ref: primitive(XmlPrimitive::Float),
1416 ..Default::default()
1417 },
1418 ],
1419 };
1420 let xml_mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(xml_struct)).unwrap();
1421 let xml_hash = compute_minimal_hash(&xml_mto).unwrap();
1422
1423 let builder_struct = TypeObjectBuilder::struct_type("Sensor")
1425 .extensibility(TypeExt::Appendable)
1426 .member("id", TypeIdentifier::Primitive(PrimitiveKind::Int64), |m| {
1427 m.key()
1428 })
1429 .member(
1430 "temp",
1431 TypeIdentifier::Primitive(PrimitiveKind::Float32),
1432 |m| m,
1433 )
1434 .build_minimal();
1435 let builder_mto = MinimalTypeObject::Struct(builder_struct);
1436 let builder_hash = compute_minimal_hash(&builder_mto).unwrap();
1437 assert_eq!(
1438 xml_hash, builder_hash,
1439 "the XML bridge and builder must produce byte-identical TypeObjects"
1440 );
1441 }
1442
1443 #[test]
1446 fn bridge_error_display() {
1447 let e = BridgeError::UnsupportedXsdConstruct("foo".into());
1448 assert!(e.to_string().contains("foo"));
1449 let e = BridgeError::UnresolvedReference("Bar".into());
1450 assert!(e.to_string().contains("Bar"));
1451 let e = BridgeError::InvalidLiteral("x".into());
1452 assert!(e.to_string().contains("x"));
1453 let e = BridgeError::HashFailed("oom".into());
1454 assert!(e.to_string().contains("oom"));
1455 let e = BridgeError::ModuleAtTopLevel("M".into());
1456 assert!(e.to_string().contains("M"));
1457 }
1458}