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_1() {
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, 1);
788 assert_eq!(st.member_seq[1].common.member_id, 2);
789 } else {
790 panic!("not a struct");
791 }
792 }
793
794 #[test]
795 fn struct_member_flags_key_optional_must_understand() {
796 use zerodds_types::type_object::flags::StructMemberFlag;
797 let s = StructType {
798 name: "X".into(),
799 extensibility: None,
800 base_type: None,
801 members: alloc::vec![StructMember {
802 name: "a".into(),
803 type_ref: primitive(XmlPrimitive::Long),
804 key: true,
805 optional: true,
806 must_understand: true,
807 ..Default::default()
808 }],
809 };
810 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
811 if let MinimalTypeObject::Struct(st) = mto {
812 let f = st.member_seq[0].common.member_flags;
813 assert!(f.has(StructMemberFlag::IS_KEY));
814 assert!(f.has(StructMemberFlag::IS_OPTIONAL));
815 assert!(f.has(StructMemberFlag::IS_MUST_UNDERSTAND));
816 } else {
817 panic!("not a struct");
818 }
819 }
820
821 #[test]
822 fn struct_string_member_with_bound_uses_string_small() {
823 let s = StructType {
824 name: "X".into(),
825 extensibility: None,
826 base_type: None,
827 members: alloc::vec![StructMember {
828 name: "name".into(),
829 type_ref: primitive(XmlPrimitive::String),
830 string_max_length: Some(64),
831 ..Default::default()
832 }],
833 };
834 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
835 if let MinimalTypeObject::Struct(st) = mto {
836 assert_eq!(
837 st.member_seq[0].common.member_type_id,
838 TypeIdentifier::String8Small { bound: 64 }
839 );
840 } else {
841 panic!();
842 }
843 }
844
845 #[test]
846 fn struct_member_with_array_dimensions_wraps_into_plain_array() {
847 let s = StructType {
848 name: "X".into(),
849 extensibility: None,
850 base_type: None,
851 members: alloc::vec![StructMember {
852 name: "a".into(),
853 type_ref: primitive(XmlPrimitive::Long),
854 array_dimensions: alloc::vec![3, 4],
855 ..Default::default()
856 }],
857 };
858 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
859 if let MinimalTypeObject::Struct(st) = mto {
860 assert!(matches!(
861 st.member_seq[0].common.member_type_id,
862 TypeIdentifier::PlainArraySmall { .. }
863 ));
864 } else {
865 panic!();
866 }
867 }
868
869 #[test]
870 fn struct_member_with_sequence_bound_wraps_into_plain_sequence() {
871 let s = StructType {
872 name: "X".into(),
873 extensibility: None,
874 base_type: None,
875 members: alloc::vec![StructMember {
876 name: "a".into(),
877 type_ref: primitive(XmlPrimitive::Long),
878 sequence_max_length: Some(100),
879 ..Default::default()
880 }],
881 };
882 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
883 if let MinimalTypeObject::Struct(st) = mto {
884 assert!(matches!(
885 st.member_seq[0].common.member_type_id,
886 TypeIdentifier::PlainSequenceSmall { bound: 100, .. }
887 ));
888 } else {
889 panic!();
890 }
891 }
892
893 #[test]
894 fn struct_member_with_large_array_uses_plain_array_large() {
895 let s = StructType {
896 name: "X".into(),
897 extensibility: None,
898 base_type: None,
899 members: alloc::vec![StructMember {
900 name: "a".into(),
901 type_ref: primitive(XmlPrimitive::Long),
902 array_dimensions: alloc::vec![300, 4],
903 ..Default::default()
904 }],
905 };
906 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
907 if let MinimalTypeObject::Struct(st) = mto {
908 assert!(matches!(
909 st.member_seq[0].common.member_type_id,
910 TypeIdentifier::PlainArrayLarge { .. }
911 ));
912 } else {
913 panic!();
914 }
915 }
916
917 #[test]
918 fn struct_extensibility_mutable_sets_flag() {
919 let mut s = make_struct();
920 s.extensibility = Some(Extensibility::Mutable);
921 let mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(s)).unwrap();
922 if let MinimalTypeObject::Struct(st) = mto {
923 assert!(st.struct_flags.has(StructTypeFlag::IS_MUTABLE));
924 } else {
925 panic!();
926 }
927 }
928
929 #[test]
932 fn enum_with_explicit_values() {
933 let e = EnumType {
934 name: "Color".into(),
935 bit_bound: Some(16),
936 enumerators: alloc::vec![
937 EnumLiteral {
938 name: "RED".into(),
939 value: Some(0),
940 },
941 EnumLiteral {
942 name: "GREEN".into(),
943 value: Some(1),
944 },
945 EnumLiteral {
946 name: "BLUE".into(),
947 value: Some(2),
948 },
949 ],
950 };
951 let mto = xml_type_to_minimal_typeobject(&TypeDef::Enum(e)).unwrap();
952 if let MinimalTypeObject::Enumerated(en) = mto {
953 assert_eq!(en.header.common.bit_bound, 16);
954 assert_eq!(en.literal_seq.len(), 3);
955 assert_eq!(en.literal_seq[2].common.value, 2);
956 } else {
957 panic!();
958 }
959 }
960
961 #[test]
962 fn enum_auto_numbering_starts_at_zero() {
963 let e = EnumType {
964 name: "X".into(),
965 bit_bound: None,
966 enumerators: alloc::vec![
967 EnumLiteral {
968 name: "A".into(),
969 value: None,
970 },
971 EnumLiteral {
972 name: "B".into(),
973 value: None,
974 },
975 EnumLiteral {
976 name: "C".into(),
977 value: Some(10),
978 },
979 EnumLiteral {
980 name: "D".into(),
981 value: None,
982 },
983 ],
984 };
985 let mto = xml_type_to_minimal_typeobject(&TypeDef::Enum(e)).unwrap();
986 if let MinimalTypeObject::Enumerated(en) = mto {
987 assert_eq!(en.literal_seq[0].common.value, 0);
988 assert_eq!(en.literal_seq[1].common.value, 1);
989 assert_eq!(en.literal_seq[2].common.value, 10);
990 assert_eq!(en.literal_seq[3].common.value, 11);
991 assert_eq!(en.header.common.bit_bound, 32);
993 } else {
994 panic!();
995 }
996 }
997
998 #[test]
1001 fn union_with_int_disc_and_default_branch() {
1002 let u = UnionType {
1003 name: "U".into(),
1004 discriminator: primitive(XmlPrimitive::Long),
1005 cases: alloc::vec![
1006 UnionCase {
1007 discriminators: alloc::vec![
1008 UnionDiscriminator::Value("1".into()),
1009 UnionDiscriminator::Value("2".into()),
1010 ],
1011 member: StructMember {
1012 name: "a".into(),
1013 type_ref: primitive(XmlPrimitive::LongLong),
1014 ..Default::default()
1015 },
1016 },
1017 UnionCase {
1018 discriminators: alloc::vec![UnionDiscriminator::Default],
1019 member: StructMember {
1020 name: "b".into(),
1021 type_ref: primitive(XmlPrimitive::String),
1022 string_max_length: Some(64),
1023 ..Default::default()
1024 },
1025 },
1026 ],
1027 };
1028 let mto = xml_type_to_minimal_typeobject(&TypeDef::Union(u)).unwrap();
1029 if let MinimalTypeObject::Union(un) = mto {
1030 assert_eq!(un.member_seq.len(), 2);
1031 assert_eq!(un.member_seq[0].common.label_seq, alloc::vec![1_i32, 2]);
1032 assert!(un.member_seq[1].common.member_flags.0 & UnionMemberFlag::IS_DEFAULT != 0);
1033 } else {
1034 panic!();
1035 }
1036 }
1037
1038 #[test]
1039 fn union_label_hex_parsed() {
1040 let u = UnionType {
1041 name: "U".into(),
1042 discriminator: primitive(XmlPrimitive::Long),
1043 cases: alloc::vec![UnionCase {
1044 discriminators: alloc::vec![UnionDiscriminator::Value("0x10".into())],
1045 member: StructMember {
1046 name: "a".into(),
1047 type_ref: primitive(XmlPrimitive::Long),
1048 ..Default::default()
1049 },
1050 }],
1051 };
1052 let mto = xml_type_to_minimal_typeobject(&TypeDef::Union(u)).unwrap();
1053 if let MinimalTypeObject::Union(un) = mto {
1054 assert_eq!(un.member_seq[0].common.label_seq, alloc::vec![16_i32]);
1055 } else {
1056 panic!();
1057 }
1058 }
1059
1060 #[test]
1061 fn union_label_invalid_returns_invalid_literal() {
1062 let u = UnionType {
1063 name: "U".into(),
1064 discriminator: primitive(XmlPrimitive::Long),
1065 cases: alloc::vec![UnionCase {
1066 discriminators: alloc::vec![UnionDiscriminator::Value("not-an-int".into())],
1067 member: StructMember {
1068 name: "a".into(),
1069 type_ref: primitive(XmlPrimitive::Long),
1070 ..Default::default()
1071 },
1072 }],
1073 };
1074 let err = xml_type_to_minimal_typeobject(&TypeDef::Union(u)).unwrap_err();
1075 assert!(matches!(err, BridgeError::InvalidLiteral(_)));
1076 }
1077
1078 #[test]
1081 fn typedef_to_alias() {
1082 let t = TypedefType {
1083 name: "Count".into(),
1084 type_ref: primitive(XmlPrimitive::ULongLong),
1085 ..Default::default()
1086 };
1087 let mto = xml_type_to_minimal_typeobject(&TypeDef::Typedef(t)).unwrap();
1088 if let MinimalTypeObject::Alias(a) = mto {
1089 assert_eq!(
1090 a.body.common.related_type,
1091 TypeIdentifier::Primitive(PrimitiveKind::UInt64)
1092 );
1093 } else {
1094 panic!("not an alias");
1095 }
1096 }
1097
1098 #[test]
1099 fn typedef_with_array_dims_lowers_to_array_type() {
1100 let t = TypedefType {
1101 name: "Vec3".into(),
1102 type_ref: primitive(XmlPrimitive::Float),
1103 array_dimensions: alloc::vec![3],
1104 ..Default::default()
1105 };
1106 let mto = xml_type_to_minimal_typeobject(&TypeDef::Typedef(t)).unwrap();
1107 match mto {
1108 MinimalTypeObject::Array(a) => {
1109 assert_eq!(a.bound_seq, alloc::vec![3]);
1110 assert_eq!(
1111 a.element.common.type_id,
1112 TypeIdentifier::Primitive(PrimitiveKind::Float32)
1113 );
1114 }
1115 other => panic!("expected array, got {other:?}"),
1116 }
1117 }
1118
1119 #[test]
1120 fn typedef_with_sequence_bound_lowers_to_sequence_type() {
1121 let t = TypedefType {
1122 name: "Bytes".into(),
1123 type_ref: primitive(XmlPrimitive::Octet),
1124 sequence_max_length: Some(1024),
1125 ..Default::default()
1126 };
1127 let mto = xml_type_to_minimal_typeobject(&TypeDef::Typedef(t)).unwrap();
1128 match mto {
1129 MinimalTypeObject::Sequence(s) => {
1130 assert_eq!(s.bound, 1024);
1131 }
1132 _ => panic!(),
1133 }
1134 }
1135
1136 #[test]
1139 fn bitmask_three_flags() {
1140 let b = BitmaskType {
1141 name: "Perms".into(),
1142 bit_bound: Some(8),
1143 bit_values: alloc::vec![
1144 BitValue {
1145 name: "READ".into(),
1146 position: Some(0),
1147 },
1148 BitValue {
1149 name: "WRITE".into(),
1150 position: Some(1),
1151 },
1152 BitValue {
1153 name: "EXEC".into(),
1154 position: Some(2),
1155 },
1156 ],
1157 };
1158 let mto = xml_type_to_minimal_typeobject(&TypeDef::Bitmask(b)).unwrap();
1159 if let MinimalTypeObject::Bitmask(bm) = mto {
1160 assert_eq!(bm.bit_bound, 8);
1161 assert_eq!(bm.flag_seq.len(), 3);
1162 assert_eq!(bm.flag_seq[0].common.position, 0);
1163 assert_eq!(bm.flag_seq[2].common.position, 2);
1164 } else {
1165 panic!();
1166 }
1167 }
1168
1169 #[test]
1170 fn bitmask_auto_position() {
1171 let b = BitmaskType {
1172 name: "X".into(),
1173 bit_bound: None,
1174 bit_values: alloc::vec![
1175 BitValue {
1176 name: "A".into(),
1177 position: None,
1178 },
1179 BitValue {
1180 name: "B".into(),
1181 position: None,
1182 },
1183 BitValue {
1184 name: "C".into(),
1185 position: Some(10),
1186 },
1187 BitValue {
1188 name: "D".into(),
1189 position: None,
1190 },
1191 ],
1192 };
1193 let mto = xml_type_to_minimal_typeobject(&TypeDef::Bitmask(b)).unwrap();
1194 if let MinimalTypeObject::Bitmask(bm) = mto {
1195 assert_eq!(bm.flag_seq[0].common.position, 0);
1196 assert_eq!(bm.flag_seq[1].common.position, 1);
1197 assert_eq!(bm.flag_seq[2].common.position, 10);
1198 assert_eq!(bm.flag_seq[3].common.position, 11);
1199 assert_eq!(bm.bit_bound, 32);
1200 } else {
1201 panic!();
1202 }
1203 }
1204
1205 #[test]
1208 fn bitset_with_two_fields() {
1209 let b = BitsetType {
1210 name: "Packed".into(),
1211 bit_fields: alloc::vec![
1212 BitField {
1213 name: "header".into(),
1214 type_ref: primitive(XmlPrimitive::ULong),
1215 mask: "0x0F".into(),
1216 },
1217 BitField {
1218 name: "body".into(),
1219 type_ref: primitive(XmlPrimitive::ULong),
1220 mask: "0xFFFFFFF0".into(),
1221 },
1222 ],
1223 };
1224 let mto = xml_type_to_minimal_typeobject(&TypeDef::Bitset(b)).unwrap();
1225 if let MinimalTypeObject::Bitset(bs) = mto {
1226 assert_eq!(bs.field_seq.len(), 2);
1227 assert_eq!(bs.field_seq[0].common.bitcount, 4);
1228 assert_eq!(bs.field_seq[0].common.position, 0);
1229 assert_eq!(bs.field_seq[1].common.bitcount, 28);
1230 assert_eq!(bs.field_seq[1].common.position, 4);
1231 } else {
1232 panic!();
1233 }
1234 }
1235
1236 #[test]
1237 fn bitset_invalid_mask_rejected() {
1238 let b = BitsetType {
1239 name: "X".into(),
1240 bit_fields: alloc::vec![BitField {
1241 name: "f".into(),
1242 type_ref: primitive(XmlPrimitive::ULong),
1243 mask: "garbage".into(),
1244 }],
1245 };
1246 let err = xml_type_to_minimal_typeobject(&TypeDef::Bitset(b)).unwrap_err();
1247 assert!(matches!(err, BridgeError::InvalidLiteral(_)));
1248 }
1249
1250 #[test]
1251 fn bitset_zero_mask_rejected() {
1252 let b = BitsetType {
1253 name: "X".into(),
1254 bit_fields: alloc::vec![BitField {
1255 name: "f".into(),
1256 type_ref: primitive(XmlPrimitive::ULong),
1257 mask: "0x00".into(),
1258 }],
1259 };
1260 let err = xml_type_to_minimal_typeobject(&TypeDef::Bitset(b)).unwrap_err();
1261 assert!(matches!(err, BridgeError::InvalidLiteral(_)));
1262 }
1263
1264 #[test]
1267 fn module_at_top_level_is_error() {
1268 let m = TypeDef::Module(ModuleEntry {
1269 name: "M".into(),
1270 types: alloc::vec![],
1271 });
1272 let err = xml_type_to_minimal_typeobject(&m).unwrap_err();
1273 assert!(matches!(err, BridgeError::ModuleAtTopLevel(_)));
1274 }
1275
1276 #[test]
1277 fn library_resolves_named_refs_between_types() {
1278 let lib = TypeLibrary {
1281 name: "Lib".into(),
1282 types: alloc::vec![
1283 TypeDef::Struct(StructType {
1284 name: "Point".into(),
1285 extensibility: None,
1286 base_type: None,
1287 members: alloc::vec![
1288 StructMember {
1289 name: "x".into(),
1290 type_ref: primitive(XmlPrimitive::Float),
1291 ..Default::default()
1292 },
1293 StructMember {
1294 name: "y".into(),
1295 type_ref: primitive(XmlPrimitive::Float),
1296 ..Default::default()
1297 },
1298 ],
1299 }),
1300 TypeDef::Struct(StructType {
1301 name: "Line".into(),
1302 extensibility: None,
1303 base_type: None,
1304 members: alloc::vec![
1305 StructMember {
1306 name: "a".into(),
1307 type_ref: TypeRef::Named("Point".into()),
1308 ..Default::default()
1309 },
1310 StructMember {
1311 name: "b".into(),
1312 type_ref: TypeRef::Named("Point".into()),
1313 ..Default::default()
1314 },
1315 ],
1316 }),
1317 ],
1318 };
1319 let map = bridge_library(&lib).unwrap();
1320 let line = map.get("Line").expect("Line registered");
1321 if let MinimalTypeObject::Struct(st) = line {
1322 assert_eq!(
1324 st.member_seq[0].common.member_type_id,
1325 st.member_seq[1].common.member_type_id
1326 );
1327 assert!(matches!(
1328 st.member_seq[0].common.member_type_id,
1329 TypeIdentifier::EquivalenceHashMinimal(_)
1330 ));
1331 } else {
1332 panic!("Line not a struct");
1333 }
1334 assert!(map.contains_key("Point"));
1336 }
1337
1338 #[test]
1339 fn library_module_flattens_to_scoped_name() {
1340 let lib = TypeLibrary {
1341 name: "L".into(),
1342 types: alloc::vec![TypeDef::Module(ModuleEntry {
1343 name: "Inner".into(),
1344 types: alloc::vec![TypeDef::Struct(StructType {
1345 name: "S".into(),
1346 extensibility: None,
1347 base_type: None,
1348 members: alloc::vec![],
1349 })],
1350 })],
1351 };
1352 let map = bridge_library(&lib).unwrap();
1353 assert!(map.contains_key("Inner::S"));
1354 }
1355
1356 #[test]
1357 fn library_unknown_named_ref_falls_back_to_zero_hash() {
1358 let lib = TypeLibrary {
1359 name: "L".into(),
1360 types: alloc::vec![TypeDef::Struct(StructType {
1361 name: "S".into(),
1362 extensibility: None,
1363 base_type: None,
1364 members: alloc::vec![StructMember {
1365 name: "missing".into(),
1366 type_ref: TypeRef::Named("DoesNotExist".into()),
1367 ..Default::default()
1368 }],
1369 })],
1370 };
1371 let map = bridge_library(&lib).unwrap();
1372 let s = map.get("S").unwrap();
1373 if let MinimalTypeObject::Struct(st) = s {
1374 assert_eq!(
1376 st.member_seq[0].common.member_type_id,
1377 TypeIdentifier::EquivalenceHashMinimal(zerodds_types::EquivalenceHash([0; 14]))
1378 );
1379 } else {
1380 panic!();
1381 }
1382 }
1383
1384 #[test]
1387 fn typeobject_can_be_hashed() {
1388 let s = make_struct();
1389 let to = xml_type_to_typeobject(&TypeDef::Struct(s)).unwrap();
1390 let h = zerodds_types::compute_hash(&to).expect("hash");
1391 let h2 = zerodds_types::compute_hash(&to).unwrap();
1393 assert_eq!(h, h2);
1394 }
1395
1396 #[test]
1397 fn xml_struct_matches_idl_struct_hash() {
1398 let xml_struct = StructType {
1402 name: "Sensor".into(),
1403 extensibility: Some(Extensibility::Appendable),
1404 base_type: None,
1405 members: alloc::vec![
1406 StructMember {
1407 name: "id".into(),
1408 type_ref: primitive(XmlPrimitive::LongLong),
1409 key: true,
1410 ..Default::default()
1411 },
1412 StructMember {
1413 name: "temp".into(),
1414 type_ref: primitive(XmlPrimitive::Float),
1415 ..Default::default()
1416 },
1417 ],
1418 };
1419 let xml_mto = xml_type_to_minimal_typeobject(&TypeDef::Struct(xml_struct)).unwrap();
1420 let xml_hash = compute_minimal_hash(&xml_mto).unwrap();
1421
1422 let builder_struct = TypeObjectBuilder::struct_type("Sensor")
1424 .extensibility(TypeExt::Appendable)
1425 .member("id", TypeIdentifier::Primitive(PrimitiveKind::Int64), |m| {
1426 m.key()
1427 })
1428 .member(
1429 "temp",
1430 TypeIdentifier::Primitive(PrimitiveKind::Float32),
1431 |m| m,
1432 )
1433 .build_minimal();
1434 let builder_mto = MinimalTypeObject::Struct(builder_struct);
1435 let builder_hash = compute_minimal_hash(&builder_mto).unwrap();
1436 assert_eq!(
1437 xml_hash, builder_hash,
1438 "XML-bridge und Builder muessen byte-identische TypeObjects produzieren"
1439 );
1440 }
1441
1442 #[test]
1445 fn bridge_error_display() {
1446 let e = BridgeError::UnsupportedXsdConstruct("foo".into());
1447 assert!(e.to_string().contains("foo"));
1448 let e = BridgeError::UnresolvedReference("Bar".into());
1449 assert!(e.to_string().contains("Bar"));
1450 let e = BridgeError::InvalidLiteral("x".into());
1451 assert!(e.to_string().contains("x"));
1452 let e = BridgeError::HashFailed("oom".into());
1453 assert!(e.to_string().contains("oom"));
1454 let e = BridgeError::ModuleAtTopLevel("M".into());
1455 assert!(e.to_string().contains("M"));
1456 }
1457}