1use indexmap::IndexMap;
17
18use crate::xsd::{self, ComplexContent, Facet, MaxOccurs, Restriction, Schema, SequenceElement};
19
20use super::types::{
21 AttrDef, Cardinality, CodeEnumDef, CodeValue, Constraint, EnumDef, FieldDef, NewtypeDef,
22 OpaqueDef, RootElement, RustType, StructDef, TypeDef, TypeGraph, TypeRef, ValueWithAttrDef,
23 VariantDef,
24};
25
26#[derive(Debug, thiserror::Error)]
30pub enum LowerError {
31 #[error("unknown base type '{base}' in simple type '{context}'")]
34 UnknownBase { base: String, context: String },
35}
36
37pub fn lower(schema: &Schema) -> Result<TypeGraph, LowerError> {
45 let mut types: IndexMap<String, TypeDef> = IndexMap::new();
46
47 for st in &schema.simple_types {
48 let def = lower_simple_type(st)?;
49 let name = normalize_type_name(&st.name);
50 types.insert(name, def);
51 }
52
53 for ct in &schema.complex_types {
54 let def = lower_complex_type(ct);
55 let name = normalize_type_name(&ct.name);
56 types.insert(name, def);
57 }
58
59 let root_elements = schema
60 .elements
61 .iter()
62 .map(|e| RootElement {
63 xml_name: e.name.clone(),
64 type_name: e.type_name.clone(),
65 })
66 .collect();
67
68 Ok(TypeGraph {
69 namespace: schema.target_namespace.clone(),
70 root_elements,
71 types,
72 })
73}
74
75fn lower_simple_type(st: &xsd::SimpleType) -> Result<TypeDef, LowerError> {
78 let restriction = &st.restriction;
79
80 let all_enums = !restriction.facets.is_empty()
82 && restriction
83 .facets
84 .iter()
85 .all(|f| matches!(f, Facet::Enumeration(_)));
86
87 if all_enums {
88 return Ok(TypeDef::CodeEnum(lower_code_enum(&st.name, restriction)));
89 }
90
91 lower_newtype(&st.name, restriction).map(TypeDef::Newtype)
93}
94
95fn lower_code_enum(name: &str, restriction: &Restriction) -> CodeEnumDef {
96 let codes = restriction
97 .facets
98 .iter()
99 .filter_map(|f| {
100 if let Facet::Enumeration(v) = f {
101 Some(CodeValue {
102 xml_value: v.clone(),
103 rust_name: code_to_pascal_case(v),
104 })
105 } else {
106 None
107 }
108 })
109 .collect();
110
111 CodeEnumDef {
112 name: normalize_type_name(name),
113 codes,
114 }
115}
116
117fn lower_newtype(name: &str, restriction: &Restriction) -> Result<NewtypeDef, LowerError> {
118 let inner = map_builtin(&restriction.base).ok_or_else(|| LowerError::UnknownBase {
119 base: restriction.base.clone(),
120 context: name.to_owned(),
121 })?;
122
123 let constraints = restriction.facets.iter().filter_map(lower_facet).collect();
124
125 Ok(NewtypeDef {
126 name: normalize_type_name(name),
127 inner,
128 constraints,
129 })
130}
131
132fn lower_facet(facet: &Facet) -> Option<Constraint> {
133 match facet {
134 Facet::Enumeration(_) => None, Facet::Pattern(v) => Some(Constraint::Pattern(v.clone())),
136 Facet::MinLength(v) => Some(Constraint::MinLength(*v)),
137 Facet::MaxLength(v) => Some(Constraint::MaxLength(*v)),
138 Facet::MinInclusive(v) => Some(Constraint::MinInclusive(v.clone())),
139 Facet::MaxInclusive(v) => Some(Constraint::MaxInclusive(v.clone())),
140 Facet::TotalDigits(v) => Some(Constraint::TotalDigits(*v)),
141 Facet::FractionDigits(v) => Some(Constraint::FractionDigits(*v)),
142 }
143}
144
145fn lower_complex_type(ct: &xsd::ComplexType) -> TypeDef {
148 match &ct.content {
149 ComplexContent::Sequence(elements) => TypeDef::Struct(lower_struct(&ct.name, elements)),
150 ComplexContent::Choice(variants) => TypeDef::Enum(lower_enum(&ct.name, variants)),
151 ComplexContent::SimpleContent { base, attributes } => {
152 TypeDef::ValueWithAttr(lower_value_with_attr(&ct.name, base, attributes))
153 }
154 ComplexContent::Any { namespace } => TypeDef::Opaque(OpaqueDef {
155 name: normalize_type_name(&ct.name),
156 namespace: namespace.clone(),
157 }),
158 }
159}
160
161fn lower_struct(name: &str, elements: &[SequenceElement]) -> StructDef {
162 let fields = elements.iter().map(lower_field).collect();
163 StructDef {
164 name: normalize_type_name(name),
165 fields,
166 }
167}
168
169fn lower_field(el: &SequenceElement) -> FieldDef {
170 let type_ref = resolve_type_ref(&el.type_name);
171 let cardinality = lower_cardinality(el.min_occurs, &el.max_occurs);
172
173 FieldDef {
174 xml_name: el.name.clone(),
175 rust_name: xml_to_snake_case(&el.name),
176 type_ref,
177 cardinality,
178 }
179}
180
181fn lower_cardinality(min_occurs: u32, max_occurs: &MaxOccurs) -> Cardinality {
182 match (min_occurs, max_occurs) {
183 (_, MaxOccurs::Unbounded) => Cardinality::Vec,
184 (_, MaxOccurs::Bounded(n)) if *n > 1 => Cardinality::BoundedVec(*n),
185 (0, MaxOccurs::Bounded(1)) => Cardinality::Optional,
186 _ => Cardinality::Required,
187 }
188}
189
190fn lower_enum(name: &str, variants: &[xsd::ChoiceVariant]) -> EnumDef {
191 let variants = variants
192 .iter()
193 .map(|v| VariantDef {
194 xml_name: v.name.clone(),
195 rust_name: v.name.clone(),
198 type_ref: resolve_type_ref(&v.type_name),
199 })
200 .collect();
201
202 EnumDef {
203 name: normalize_type_name(name),
204 variants,
205 }
206}
207
208fn lower_value_with_attr(
209 name: &str,
210 base: &str,
211 attributes: &[xsd::Attribute],
212) -> ValueWithAttrDef {
213 let value_type = resolve_type_ref(base);
214
215 let attrs = attributes
216 .iter()
217 .map(|a| AttrDef {
218 xml_name: a.name.clone(),
219 rust_name: xml_to_snake_case(&a.name),
220 type_ref: resolve_type_ref(&a.type_name),
221 required: a.required,
222 })
223 .collect();
224
225 ValueWithAttrDef {
226 name: normalize_type_name(name),
227 value_type,
228 attributes: attrs,
229 }
230}
231
232fn normalize_type_name(name: &str) -> String {
241 if name.contains('_') {
242 name.split('_')
243 .map(|seg| {
244 let mut c = seg.chars();
245 match c.next() {
246 None => String::new(),
247 Some(first) => {
248 let mut s = first.to_uppercase().to_string();
249 s.extend(c);
250 s
251 }
252 }
253 })
254 .collect()
255 } else {
256 name.to_owned()
257 }
258}
259
260fn resolve_type_ref(type_name: &str) -> TypeRef {
266 if let Some(rt) = map_builtin(type_name) {
267 TypeRef::Builtin(rt)
268 } else {
269 TypeRef::Named(normalize_type_name(type_name))
270 }
271}
272
273fn map_builtin(name: &str) -> Option<RustType> {
277 let local = name.split(':').next_back().unwrap_or(name);
279
280 match local {
281 "string" | "normalizedString" | "token" | "ID" | "IDREF" | "NMTOKEN" | "anyURI"
282 | "language" | "Name" | "NCName" | "base64Binary" | "hexBinary" | "time" | "gYear"
283 | "gYearMonth" | "gMonth" | "gMonthDay" | "gDay" | "duration" => Some(RustType::String),
284 "boolean" => Some(RustType::Bool),
285 "decimal" | "integer" | "int" | "long" | "short" | "byte" | "nonNegativeInteger"
286 | "positiveInteger" | "unsignedInt" | "unsignedLong" | "unsignedShort" | "unsignedByte"
287 | "nonPositiveInteger" | "negativeInteger" | "float" | "double" => Some(RustType::Decimal),
288 "date" => Some(RustType::Date),
289 "dateTime" => Some(RustType::DateTime),
290 _ => None,
291 }
292}
293
294pub fn xml_to_snake_case(name: &str) -> String {
321 if name.is_empty() {
322 return String::new();
323 }
324
325 let chars: Vec<char> = name.chars().collect();
326 let n = chars.len();
327 let mut out = String::with_capacity(n + 4);
328
329 for i in 0..n {
330 let c = chars[i];
331 if c.is_uppercase() {
332 let prev_lower = i > 0 && chars[i - 1].is_lowercase();
333 let next_lower = i + 1 < n && chars[i + 1].is_lowercase();
334 let prev_upper = i > 0 && chars[i - 1].is_uppercase();
335
336 if i > 0 && (prev_lower || (prev_upper && next_lower)) {
342 out.push('_');
343 }
344 out.push(c.to_lowercase().next().unwrap_or(c));
345 } else {
346 out.push(c);
347 }
348 }
349
350 out
351}
352
353pub fn code_to_pascal_case(value: &str) -> String {
377 value
379 .split('-')
380 .map(|segment| {
381 let mut chars = segment.chars();
382 match chars.next() {
383 None => String::new(),
384 Some(first) => {
385 let upper = first.to_uppercase().to_string();
386 let rest: String = chars.collect::<String>().to_lowercase();
387 upper + &rest
388 }
389 }
390 })
391 .collect()
392}
393
394#[cfg(test)]
397mod tests {
398 use super::*;
399 use crate::ir::types::{Cardinality, CodeValue, Constraint, RustType, TypeDef, TypeRef};
400 use crate::xsd::{
401 Attribute, ChoiceVariant, ComplexContent, ComplexType, Element, Facet, MaxOccurs,
402 Restriction, Schema, SequenceElement, SimpleType,
403 };
404
405 fn make_schema(
408 elements: Vec<Element>,
409 simple_types: Vec<SimpleType>,
410 complex_types: Vec<ComplexType>,
411 ) -> Schema {
412 Schema {
413 target_namespace: "urn:test".to_owned(),
414 elements,
415 simple_types,
416 complex_types,
417 }
418 }
419
420 fn seq_el(name: &str, type_name: &str, min: u32, max: MaxOccurs) -> SequenceElement {
421 SequenceElement {
422 name: name.to_owned(),
423 type_name: type_name.to_owned(),
424 min_occurs: min,
425 max_occurs: max,
426 }
427 }
428
429 fn restriction(base: &str, facets: Vec<Facet>) -> Restriction {
430 Restriction {
431 base: base.to_owned(),
432 facets,
433 }
434 }
435
436 #[test]
439 fn snake_case_basic_pascal() {
440 assert_eq!(xml_to_snake_case("BizMsgIdr"), "biz_msg_idr");
441 }
442
443 #[test]
444 fn snake_case_fi_to_fi() {
445 assert_eq!(
446 xml_to_snake_case("FIToFICstmrCdtTrf"),
447 "fi_to_fi_cstmr_cdt_trf"
448 );
449 }
450
451 #[test]
452 fn snake_case_all_caps() {
453 assert_eq!(xml_to_snake_case("BICFI"), "bicfi");
454 assert_eq!(xml_to_snake_case("LEI"), "lei");
455 }
456
457 #[test]
458 fn snake_case_two_chars() {
459 assert_eq!(xml_to_snake_case("Id"), "id");
460 }
461
462 #[test]
463 fn snake_case_url_prefix() {
464 assert_eq!(xml_to_snake_case("URLAdr"), "url_adr");
465 }
466
467 #[test]
468 fn snake_case_cre_dt() {
469 assert_eq!(xml_to_snake_case("CreDt"), "cre_dt");
470 }
471
472 #[test]
473 fn snake_case_single_lower() {
474 assert_eq!(xml_to_snake_case("a"), "a");
475 }
476
477 #[test]
478 fn snake_case_empty() {
479 assert_eq!(xml_to_snake_case(""), "");
480 }
481
482 #[test]
485 fn code_pascal_addr() {
486 assert_eq!(code_to_pascal_case("ADDR"), "Addr");
487 }
488
489 #[test]
490 fn code_pascal_codu() {
491 assert_eq!(code_to_pascal_case("CODU"), "Codu");
492 }
493
494 #[test]
495 fn code_pascal_hyphen() {
496 assert_eq!(code_to_pascal_case("TEST-VALUE"), "TestValue");
497 }
498
499 #[test]
502 fn lower_code_enum_basic() {
503 let schema = make_schema(
504 vec![],
505 vec![SimpleType {
506 name: "AddressType2Code".to_owned(),
507 restriction: restriction(
508 "xs:string",
509 vec![
510 Facet::Enumeration("ADDR".to_owned()),
511 Facet::Enumeration("PBOX".to_owned()),
512 Facet::Enumeration("HOME".to_owned()),
513 ],
514 ),
515 }],
516 vec![],
517 );
518
519 let graph = lower(&schema).unwrap();
520 let def = graph.types.get("AddressType2Code").unwrap();
521 let TypeDef::CodeEnum(ce) = def else {
522 panic!("expected CodeEnum, got {:?}", def);
523 };
524
525 assert_eq!(ce.name, "AddressType2Code");
526 assert_eq!(ce.codes.len(), 3);
527 assert_eq!(
528 ce.codes[0],
529 CodeValue {
530 xml_value: "ADDR".to_owned(),
531 rust_name: "Addr".to_owned(),
532 }
533 );
534 assert_eq!(
535 ce.codes[1],
536 CodeValue {
537 xml_value: "PBOX".to_owned(),
538 rust_name: "Pbox".to_owned(),
539 }
540 );
541 }
542
543 #[test]
546 fn lower_newtype_with_pattern() {
547 let schema = make_schema(
548 vec![],
549 vec![SimpleType {
550 name: "BICFIDec2014Identifier".to_owned(),
551 restriction: restriction(
552 "xs:string",
553 vec![Facet::Pattern(
554 "[A-Z0-9]{4,4}[A-Z]{2,2}[A-Z0-9]{2,2}([A-Z0-9]{3,3}){0,1}".to_owned(),
555 )],
556 ),
557 }],
558 vec![],
559 );
560
561 let graph = lower(&schema).unwrap();
562 let def = graph.types.get("BICFIDec2014Identifier").unwrap();
563 let TypeDef::Newtype(nt) = def else {
564 panic!("expected Newtype");
565 };
566
567 assert_eq!(nt.inner, RustType::String);
568 assert_eq!(nt.constraints.len(), 1);
569 assert!(matches!(&nt.constraints[0], Constraint::Pattern(_)));
570 }
571
572 #[test]
573 fn lower_newtype_with_length_bounds() {
574 let schema = make_schema(
575 vec![],
576 vec![SimpleType {
577 name: "Max35Text".to_owned(),
578 restriction: restriction(
579 "xs:string",
580 vec![Facet::MinLength(1), Facet::MaxLength(35)],
581 ),
582 }],
583 vec![],
584 );
585
586 let graph = lower(&schema).unwrap();
587 let def = graph.types.get("Max35Text").unwrap();
588 let TypeDef::Newtype(nt) = def else {
589 panic!("expected Newtype");
590 };
591
592 assert_eq!(nt.constraints.len(), 2);
593 assert_eq!(nt.constraints[0], Constraint::MinLength(1));
594 assert_eq!(nt.constraints[1], Constraint::MaxLength(35));
595 }
596
597 #[test]
598 fn lower_newtype_no_facets() {
599 let schema = make_schema(
601 vec![],
602 vec![SimpleType {
603 name: "BusinessMessagePriorityCode".to_owned(),
604 restriction: restriction("xs:string", vec![]),
605 }],
606 vec![],
607 );
608
609 let graph = lower(&schema).unwrap();
610 let def = graph.types.get("BusinessMessagePriorityCode").unwrap();
611 let TypeDef::Newtype(nt) = def else {
612 panic!("expected Newtype");
613 };
614 assert_eq!(nt.inner, RustType::String);
615 assert!(nt.constraints.is_empty());
616 }
617
618 #[test]
621 fn lower_struct_basic() {
622 let schema = make_schema(
623 vec![],
624 vec![],
625 vec![ComplexType {
626 name: "BusinessApplicationHeaderV04".to_owned(),
627 content: ComplexContent::Sequence(vec![
628 seq_el("BizMsgIdr", "Max35Text", 1, MaxOccurs::Bounded(1)),
629 seq_el("Fr", "Party51Choice", 1, MaxOccurs::Bounded(1)),
630 seq_el(
631 "Rltd",
632 "BusinessApplicationHeader8",
633 0,
634 MaxOccurs::Unbounded,
635 ),
636 ]),
637 }],
638 );
639
640 let graph = lower(&schema).unwrap();
641 let def = graph.types.get("BusinessApplicationHeaderV04").unwrap();
642 let TypeDef::Struct(sd) = def else {
643 panic!("expected Struct");
644 };
645
646 assert_eq!(sd.name, "BusinessApplicationHeaderV04");
647 assert_eq!(sd.fields.len(), 3);
648
649 let f0 = &sd.fields[0];
650 assert_eq!(f0.xml_name, "BizMsgIdr");
651 assert_eq!(f0.rust_name, "biz_msg_idr");
652 assert_eq!(f0.type_ref, TypeRef::Named("Max35Text".to_owned()));
653 assert_eq!(f0.cardinality, Cardinality::Required);
654
655 let f2 = &sd.fields[2];
656 assert_eq!(f2.cardinality, Cardinality::Vec);
657 }
658
659 #[test]
662 fn cardinality_required() {
663 let c = lower_cardinality(1, &MaxOccurs::Bounded(1));
664 assert_eq!(c, Cardinality::Required);
665 }
666
667 #[test]
668 fn cardinality_optional() {
669 let c = lower_cardinality(0, &MaxOccurs::Bounded(1));
670 assert_eq!(c, Cardinality::Optional);
671 }
672
673 #[test]
674 fn cardinality_vec_unbounded() {
675 let c = lower_cardinality(0, &MaxOccurs::Unbounded);
676 assert_eq!(c, Cardinality::Vec);
677 }
678
679 #[test]
680 fn cardinality_bounded_vec() {
681 let c = lower_cardinality(0, &MaxOccurs::Bounded(5));
682 assert_eq!(c, Cardinality::BoundedVec(5));
683 }
684
685 #[test]
686 fn cardinality_unbounded_required_min() {
687 let c = lower_cardinality(1, &MaxOccurs::Unbounded);
690 assert_eq!(c, Cardinality::Vec);
691 }
692
693 #[test]
696 fn lower_choice_enum() {
697 let schema = make_schema(
698 vec![],
699 vec![],
700 vec![ComplexType {
701 name: "AddressType3Choice".to_owned(),
702 content: ComplexContent::Choice(vec![
703 ChoiceVariant {
704 name: "Cd".to_owned(),
705 type_name: "AddressType2Code".to_owned(),
706 },
707 ChoiceVariant {
708 name: "Prtry".to_owned(),
709 type_name: "GenericIdentification30".to_owned(),
710 },
711 ]),
712 }],
713 );
714
715 let graph = lower(&schema).unwrap();
716 let def = graph.types.get("AddressType3Choice").unwrap();
717 let TypeDef::Enum(ed) = def else {
718 panic!("expected Enum");
719 };
720
721 assert_eq!(ed.name, "AddressType3Choice");
722 assert_eq!(ed.variants.len(), 2);
723 assert_eq!(ed.variants[0].xml_name, "Cd");
724 assert_eq!(ed.variants[0].rust_name, "Cd");
725 assert_eq!(
726 ed.variants[0].type_ref,
727 TypeRef::Named("AddressType2Code".to_owned())
728 );
729 }
730
731 #[test]
734 fn lower_simple_content() {
735 let schema = make_schema(
736 vec![],
737 vec![],
738 vec![ComplexType {
739 name: "ActiveCurrencyAndAmount".to_owned(),
740 content: ComplexContent::SimpleContent {
741 base: "xs:decimal".to_owned(),
742 attributes: vec![Attribute {
743 name: "Ccy".to_owned(),
744 type_name: "ActiveCurrencyCode".to_owned(),
745 required: true,
746 }],
747 },
748 }],
749 );
750
751 let graph = lower(&schema).unwrap();
752 let def = graph.types.get("ActiveCurrencyAndAmount").unwrap();
753 let TypeDef::ValueWithAttr(vwa) = def else {
754 panic!("expected ValueWithAttr");
755 };
756
757 assert_eq!(vwa.name, "ActiveCurrencyAndAmount");
758 assert_eq!(vwa.value_type, TypeRef::Builtin(RustType::Decimal));
759 assert_eq!(vwa.attributes.len(), 1);
760 assert_eq!(vwa.attributes[0].xml_name, "Ccy");
761 assert_eq!(vwa.attributes[0].rust_name, "ccy");
762 assert_eq!(
763 vwa.attributes[0].type_ref,
764 TypeRef::Named("ActiveCurrencyCode".to_owned())
765 );
766 assert!(vwa.attributes[0].required);
767 }
768
769 #[test]
772 fn lower_any_opaque() {
773 let schema = make_schema(
774 vec![],
775 vec![],
776 vec![ComplexType {
777 name: "SignatureEnvelope".to_owned(),
778 content: ComplexContent::Any {
779 namespace: Some("##other".to_owned()),
780 },
781 }],
782 );
783
784 let graph = lower(&schema).unwrap();
785 let def = graph.types.get("SignatureEnvelope").unwrap();
786 let TypeDef::Opaque(op) = def else {
787 panic!("expected Opaque");
788 };
789
790 assert_eq!(op.name, "SignatureEnvelope");
791 assert_eq!(op.namespace, Some("##other".to_owned()));
792 }
793
794 #[test]
795 fn lower_any_opaque_no_namespace() {
796 let schema = make_schema(
797 vec![],
798 vec![],
799 vec![ComplexType {
800 name: "AnyContent".to_owned(),
801 content: ComplexContent::Any { namespace: None },
802 }],
803 );
804
805 let graph = lower(&schema).unwrap();
806 let def = graph.types.get("AnyContent").unwrap();
807 let TypeDef::Opaque(op) = def else {
808 panic!("expected Opaque");
809 };
810 assert!(op.namespace.is_none());
811 }
812
813 #[test]
816 fn builtin_mapping_string() {
817 assert_eq!(map_builtin("xs:string"), Some(RustType::String));
818 assert_eq!(map_builtin("string"), Some(RustType::String));
819 }
820
821 #[test]
822 fn builtin_mapping_boolean() {
823 assert_eq!(map_builtin("xs:boolean"), Some(RustType::Bool));
824 }
825
826 #[test]
827 fn builtin_mapping_decimal() {
828 assert_eq!(map_builtin("xs:decimal"), Some(RustType::Decimal));
829 }
830
831 #[test]
832 fn builtin_mapping_date() {
833 assert_eq!(map_builtin("xs:date"), Some(RustType::Date));
834 }
835
836 #[test]
837 fn builtin_mapping_datetime() {
838 assert_eq!(map_builtin("xs:dateTime"), Some(RustType::DateTime));
839 }
840
841 #[test]
842 fn builtin_mapping_unknown() {
843 assert_eq!(map_builtin("Max35Text"), None);
844 }
845
846 #[test]
849 fn lower_root_elements() {
850 let schema = make_schema(
851 vec![Element {
852 name: "AppHdr".to_owned(),
853 type_name: "BusinessApplicationHeaderV04".to_owned(),
854 }],
855 vec![],
856 vec![],
857 );
858
859 let graph = lower(&schema).unwrap();
860 assert_eq!(graph.root_elements.len(), 1);
861 assert_eq!(graph.root_elements[0].xml_name, "AppHdr");
862 assert_eq!(
863 graph.root_elements[0].type_name,
864 "BusinessApplicationHeaderV04"
865 );
866 }
867
868 #[test]
871 fn integration_lower_head_001() {
872 let xsd_path = concat!(
873 env!("CARGO_MANIFEST_DIR"),
874 "/../../schemas/head/head.001.001.04.xsd"
875 );
876
877 let file = std::fs::File::open(xsd_path).expect("head.001.001.04.xsd not found");
878 let schema = crate::xsd::parse(std::io::BufReader::new(file)).expect("XSD parse failed");
879
880 let graph = lower(&schema).unwrap();
881
882 assert_eq!(graph.root_elements.len(), 1, "expected 1 root element");
884 assert_eq!(graph.root_elements[0].xml_name, "AppHdr");
885
886 assert!(
888 graph.types.len() >= 30,
889 "expected at least 30 types, got {}",
890 graph.types.len()
891 );
892
893 let root_type_name = &graph.root_elements[0].type_name;
895 let root = graph.types.get(root_type_name).expect("root type missing");
896 assert!(
897 matches!(root, TypeDef::Struct(_)),
898 "root type should be a Struct"
899 );
900
901 let addr_code = graph
903 .types
904 .get("AddressType2Code")
905 .expect("AddressType2Code missing");
906 let TypeDef::CodeEnum(ce) = addr_code else {
907 panic!("AddressType2Code should be CodeEnum");
908 };
909 assert_eq!(ce.codes.len(), 6);
910
911 let bic = graph
913 .types
914 .get("BICFIDec2014Identifier")
915 .expect("BICFIDec2014Identifier missing");
916 let TypeDef::Newtype(nt) = bic else {
917 panic!("BICFIDec2014Identifier should be Newtype");
918 };
919 assert!(nt
920 .constraints
921 .iter()
922 .any(|c| matches!(c, Constraint::Pattern(_))));
923
924 let choice = graph
926 .types
927 .get("AddressType3Choice")
928 .expect("AddressType3Choice missing");
929 assert!(matches!(choice, TypeDef::Enum(_)));
930
931 let sig = graph
933 .types
934 .get("SignatureEnvelope")
935 .expect("SignatureEnvelope missing");
936 assert!(matches!(sig, TypeDef::Opaque(_)));
937 }
938}