use super::metadata::{ElementMeta, ImportMetadata, SourceInfo};
use super::model::{Element, ElementId, ElementKind, Model, RelationshipKind, Visibility};
use std::collections::HashMap;
use std::fmt::Write;
pub struct DecompileResult {
pub text: String,
pub metadata: ImportMetadata,
}
pub fn decompile(model: &Model) -> DecompileResult {
let mut ctx = DecompileContext::new(model);
ctx.decompile_model();
DecompileResult {
text: ctx.output,
metadata: ctx.metadata,
}
}
pub fn decompile_with_source(model: &Model, source: SourceInfo) -> DecompileResult {
let mut ctx = DecompileContext::new(model);
ctx.metadata.source = source;
ctx.decompile_model();
DecompileResult {
text: ctx.output,
metadata: ctx.metadata,
}
}
struct DecompileContext<'a> {
model: &'a Model,
output: String,
metadata: ImportMetadata,
indent_level: usize,
qualified_names: HashMap<ElementId, String>,
sibling_order: HashMap<ElementId, u32>,
}
impl<'a> DecompileContext<'a> {
fn new(model: &'a Model) -> Self {
Self {
model,
output: String::new(),
metadata: ImportMetadata::new(),
indent_level: 0,
qualified_names: HashMap::new(),
sibling_order: HashMap::new(),
}
}
fn decompile_model(&mut self) {
self.compute_qualified_names();
for root_id in &self.model.roots {
if let Some(element) = self.model.elements.get(root_id) {
self.decompile_element(element);
}
}
}
fn compute_qualified_names(&mut self) {
for (id, _element) in &self.model.elements {
let qn = self.compute_qualified_name(id.clone());
self.qualified_names.insert(id.clone(), qn);
}
}
fn compute_qualified_name(&self, id: ElementId) -> String {
let Some(element) = self.model.elements.get(&id) else {
return id.as_str().to_string();
};
let name = element
.name
.as_deref()
.unwrap_or_else(|| element.id.as_str());
if let Some(owner_id) = &element.owner {
if self.model.elements.contains_key(owner_id) {
let owner_qn = self.compute_qualified_name(owner_id.clone());
return format!("{}::{}", owner_qn, name);
}
}
name.to_string()
}
fn decompile_element(&mut self, element: &Element) {
if element.kind.is_relationship() {
self.decompile_transparent_container(element);
return;
}
self.record_metadata(element);
match element.kind {
ElementKind::Package => self.decompile_package(element),
ElementKind::LibraryPackage => self.decompile_library_package(element),
ElementKind::PartDefinition => self.decompile_definition(element, "part def"),
ElementKind::ItemDefinition => self.decompile_definition(element, "item def"),
ElementKind::PortDefinition => self.decompile_definition(element, "port def"),
ElementKind::AttributeDefinition => self.decompile_definition(element, "attribute def"),
ElementKind::ActionDefinition => self.decompile_definition(element, "action def"),
ElementKind::ConnectionDefinition => {
self.decompile_definition(element, "connection def")
}
ElementKind::InterfaceDefinition => self.decompile_definition(element, "interface def"),
ElementKind::AllocationDefinition => {
self.decompile_definition(element, "allocation def")
}
ElementKind::RequirementDefinition => {
self.decompile_definition(element, "requirement def")
}
ElementKind::ConstraintDefinition => {
self.decompile_definition(element, "constraint def")
}
ElementKind::StateDefinition => self.decompile_definition(element, "state def"),
ElementKind::CalculationDefinition => self.decompile_definition(element, "calc def"),
ElementKind::UseCaseDefinition => self.decompile_definition(element, "use case def"),
ElementKind::AnalysisCaseDefinition => {
self.decompile_definition(element, "analysis def")
}
ElementKind::ViewDefinition => self.decompile_definition(element, "view def"),
ElementKind::ViewpointDefinition => self.decompile_definition(element, "viewpoint def"),
ElementKind::RenderingDefinition => self.decompile_definition(element, "rendering def"),
ElementKind::EnumerationDefinition => self.decompile_definition(element, "enum def"),
ElementKind::MetadataDefinition => self.decompile_definition(element, "metadata def"),
ElementKind::ConcernDefinition => self.decompile_definition(element, "concern def"),
ElementKind::PartUsage => self.decompile_usage(element, "part"),
ElementKind::ItemUsage => self.decompile_usage(element, "item"),
ElementKind::PortUsage => self.decompile_usage(element, "port"),
ElementKind::AttributeUsage => self.decompile_usage(element, "attribute"),
ElementKind::ActionUsage => self.decompile_usage(element, "action"),
ElementKind::ConnectionUsage => self.decompile_usage(element, "connection"),
ElementKind::InterfaceUsage => self.decompile_usage(element, "interface"),
ElementKind::AllocationUsage => self.decompile_usage(element, "allocation"),
ElementKind::RequirementUsage => self.decompile_usage(element, "requirement"),
ElementKind::ConstraintUsage => self.decompile_usage(element, "constraint"),
ElementKind::StateUsage => self.decompile_usage(element, "state"),
ElementKind::CalculationUsage => self.decompile_usage(element, "calc"),
ElementKind::ReferenceUsage => self.decompile_usage(element, "ref"),
ElementKind::OccurrenceUsage => self.decompile_usage(element, "occurrence"),
ElementKind::Class => self.decompile_definition(element, "class"),
ElementKind::DataType => self.decompile_definition(element, "datatype"),
ElementKind::Structure => self.decompile_definition(element, "struct"),
ElementKind::Comment => self.decompile_comment(element),
ElementKind::Documentation => self.decompile_documentation(element),
ElementKind::Other => self.decompile_transparent_container(element),
_ => {}
}
}
fn record_metadata(&mut self, element: &Element) {
let qn = self
.qualified_names
.get(&element.id)
.cloned()
.unwrap_or_else(|| element.id.as_str().to_string());
let mut meta = ElementMeta::with_id(element.id.as_str());
if let Some(owner_id) = &element.owner {
let order = self.sibling_order.entry(owner_id.clone()).or_insert(0);
meta = meta.with_order(*order);
*order += 1;
}
for (key, value) in &element.properties {
let json_value = property_to_json(value);
meta = meta.with_unmapped(key.as_ref(), json_value);
}
self.metadata.add_element(qn, meta);
}
fn indent(&self) -> String {
" ".repeat(self.indent_level)
}
fn write_line(&mut self, text: &str) {
let indent = self.indent();
let _ = writeln!(self.output, "{}{}", indent, text);
}
fn write_blank_line(&mut self) {
let _ = writeln!(self.output);
}
fn decompile_package(&mut self, element: &Element) {
self.write_visibility(element);
if let Some(name) = &element.name {
let short = self.format_short_name(element);
self.write_line(&format!("package {}{} {{", short, name));
} else {
self.write_line("package {");
}
self.decompile_body(element);
self.write_line("}");
self.write_blank_line();
}
fn decompile_library_package(&mut self, element: &Element) {
self.write_visibility(element);
if let Some(name) = &element.name {
let short = self.format_short_name(element);
self.write_line(&format!("library package {}{} {{", short, name));
} else {
self.write_line("library package {");
}
self.decompile_body(element);
self.write_line("}");
self.write_blank_line();
}
fn decompile_definition(&mut self, element: &Element, keyword: &str) {
self.write_visibility(element);
let abstract_kw = if element.is_abstract { "abstract " } else { "" };
let short = self.format_short_name(element);
let specializations = self.format_specializations(&element.id);
if let Some(name) = &element.name {
if element.owned_elements.is_empty() && element.documentation.is_none() {
self.write_line(&format!(
"{}{} {}{}{};",
abstract_kw, keyword, short, name, specializations
));
} else {
self.write_line(&format!(
"{}{} {}{}{} {{",
abstract_kw, keyword, short, name, specializations
));
self.decompile_body(element);
self.write_line("}");
}
} else {
self.write_line(&format!("{}{} {{{}", abstract_kw, keyword, specializations));
self.decompile_body(element);
self.write_line("}");
}
self.write_blank_line();
}
fn decompile_usage(&mut self, element: &Element, keyword: &str) {
self.write_visibility(element);
let typing = self.format_typing(&element.id);
let subsetting = self.format_subsetting(&element.id);
let redefinition = self.format_redefinition(&element.id);
let short = self.format_short_name(element);
let relations = format!("{}{}{}", typing, subsetting, redefinition);
if let Some(name) = &element.name {
if element.owned_elements.is_empty() && element.documentation.is_none() {
self.write_line(&format!("{} {}{}{};", keyword, short, name, relations));
} else {
self.write_line(&format!("{} {}{}{} {{", keyword, short, name, relations));
self.decompile_body(element);
self.write_line("}");
}
} else if !relations.is_empty() {
self.write_line(&format!("{}{};", keyword, relations));
}
}
fn decompile_comment(&mut self, element: &Element) {
if let Some(doc) = &element.documentation {
if doc.contains('\n') {
self.write_line(&format!("/* {} */", doc));
} else {
self.write_line(&format!("// {}", doc));
}
}
}
fn decompile_documentation(&mut self, element: &Element) {
if let Some(doc) = &element.documentation {
self.write_line(&format!("doc /* {} */", doc));
}
}
fn decompile_transparent_container(&mut self, element: &Element) {
for child_id in &element.owned_elements {
if let Some(child) = self.model.elements.get(child_id) {
self.decompile_element(child);
}
}
}
fn decompile_body(&mut self, element: &Element) {
self.indent_level += 1;
self.decompile_imports(&element.id);
if let Some(doc) = &element.documentation {
self.write_line(&format!("doc /* {} */", doc));
}
self.decompile_children_inner(element);
self.indent_level -= 1;
}
fn decompile_children_inner(&mut self, element: &Element) {
for child_id in &element.owned_elements {
if let Some(child) = self.model.elements.get(child_id) {
self.decompile_element(child);
}
}
}
fn write_visibility(&mut self, element: &Element) {
match element.visibility {
Visibility::Private => {
let _ = write!(self.output, "{}private ", self.indent());
}
Visibility::Protected => {
let _ = write!(self.output, "{}protected ", self.indent());
}
Visibility::Public => {}
}
}
fn format_short_name(&self, element: &Element) -> String {
if let Some(short) = &element.short_name {
format!("<{}> ", short)
} else {
String::new()
}
}
fn format_specializations(&self, element_id: &ElementId) -> String {
let specializations: Vec<&str> = self
.model
.relationships
.iter()
.filter(|r| &r.source == element_id && r.kind == RelationshipKind::Specialization)
.filter_map(|r| {
self.model
.elements
.get(&r.target)
.and_then(|e| e.name.as_deref())
})
.collect();
if specializations.is_empty() {
String::new()
} else {
format!(" :> {}", specializations.join(", "))
}
}
fn format_typing(&self, element_id: &ElementId) -> String {
let types: Vec<&str> = self
.model
.relationships
.iter()
.filter(|r| &r.source == element_id && r.kind == RelationshipKind::FeatureTyping)
.filter_map(|r| {
self.model
.elements
.get(&r.target)
.and_then(|e| e.name.as_deref())
})
.collect();
if types.is_empty() {
String::new()
} else {
format!(" : {}", types.join(", "))
}
}
fn get_namespace_imports(&self, element_id: &ElementId) -> Vec<String> {
self.model
.relationships
.iter()
.filter(|r| {
&r.owner == &Some(element_id.clone()) && r.kind == RelationshipKind::NamespaceImport
})
.filter_map(|r| {
self.model
.elements
.get(&r.target)
.and_then(|e| e.qualified_name.as_deref().or(e.name.as_deref()))
.map(|n| format!("{}::*", n))
})
.collect()
}
fn get_membership_imports(&self, element_id: &ElementId) -> Vec<String> {
self.model
.relationships
.iter()
.filter(|r| {
&r.owner == &Some(element_id.clone())
&& r.kind == RelationshipKind::MembershipImport
})
.filter_map(|r| {
self.model
.elements
.get(&r.target)
.and_then(|e| e.qualified_name.as_deref().or(e.name.as_deref()))
.map(|n| n.to_string())
})
.collect()
}
fn decompile_imports(&mut self, element_id: &ElementId) {
for import_path in self.get_namespace_imports(element_id) {
self.write_line(&format!("import {};", import_path));
}
for import_path in self.get_membership_imports(element_id) {
self.write_line(&format!("import {};", import_path));
}
}
fn format_subsetting(&self, element_id: &ElementId) -> String {
let subsets: Vec<&str> = self
.model
.relationships
.iter()
.filter(|r| &r.source == element_id && r.kind == RelationshipKind::Subsetting)
.filter_map(|r| {
self.model
.elements
.get(&r.target)
.and_then(|e| e.name.as_deref())
})
.collect();
if subsets.is_empty() {
String::new()
} else {
format!(" subsets {}", subsets.join(", "))
}
}
fn format_redefinition(&self, element_id: &ElementId) -> String {
let redefines: Vec<&str> = self
.model
.relationships
.iter()
.filter(|r| &r.source == element_id && r.kind == RelationshipKind::Redefinition)
.filter_map(|r| {
self.model
.elements
.get(&r.target)
.and_then(|e| e.name.as_deref())
})
.collect();
if redefines.is_empty() {
String::new()
} else {
format!(" redefines {}", redefines.join(", "))
}
}
}
fn property_to_json(value: &super::model::PropertyValue) -> serde_json::Value {
use super::model::PropertyValue;
match value {
PropertyValue::String(s) => serde_json::Value::String(s.to_string()),
PropertyValue::Integer(i) => serde_json::Value::Number((*i).into()),
PropertyValue::Real(f) => serde_json::json!(*f),
PropertyValue::Boolean(b) => serde_json::Value::Bool(*b),
PropertyValue::Reference(id) => serde_json::Value::String(format!("ref:{}", id.as_str())),
PropertyValue::List(items) => {
serde_json::Value::Array(items.iter().map(property_to_json).collect())
}
}
}
#[cfg(test)]
mod tests {
use super::super::model::Relationship;
use super::*;
#[test]
fn test_decompile_empty_model() {
let model = Model::new();
let result = decompile(&model);
assert!(result.text.is_empty());
assert_eq!(result.metadata.version, ImportMetadata::CURRENT_VERSION);
}
#[test]
fn test_decompile_simple_package() {
let mut model = Model::new();
let pkg = Element::new("pkg-1", ElementKind::Package).with_name("MyPackage");
model.elements.insert(pkg.id.clone(), pkg);
model.roots.push(ElementId::from("pkg-1"));
let result = decompile(&model);
assert!(result.text.contains("package MyPackage {"));
assert!(result.text.contains("}"));
let meta = result.metadata.get_element("MyPackage").unwrap();
assert_eq!(meta.original_id.as_deref(), Some("pkg-1"));
}
#[test]
fn test_decompile_part_definition() {
let mut model = Model::new();
let def = Element::new("def-1", ElementKind::PartDefinition).with_name("Vehicle");
model.elements.insert(def.id.clone(), def);
model.roots.push(ElementId::from("def-1"));
let result = decompile(&model);
assert!(result.text.contains("part def Vehicle;"));
}
#[test]
fn test_decompile_abstract_definition() {
let mut model = Model::new();
let mut def = Element::new("def-1", ElementKind::PartDefinition).with_name("AbstractPart");
def.is_abstract = true;
model.elements.insert(def.id.clone(), def);
model.roots.push(ElementId::from("def-1"));
let result = decompile(&model);
assert!(result.text.contains("abstract part def AbstractPart;"));
}
#[test]
fn test_decompile_definition_with_short_name() {
let mut model = Model::new();
let def = Element::new("def-1", ElementKind::PartDefinition)
.with_name("Vehicle")
.with_short_name("V");
model.elements.insert(def.id.clone(), def);
model.roots.push(ElementId::from("def-1"));
let result = decompile(&model);
assert!(result.text.contains("part def <V> Vehicle;"));
}
#[test]
fn test_decompile_nested_elements() {
let mut model = Model::new();
let pkg_id = ElementId::from("pkg-1");
let def_id = ElementId::from("def-1");
let mut pkg = Element::new(pkg_id.clone(), ElementKind::Package).with_name("MyPackage");
pkg.owned_elements.push(def_id.clone());
let def = Element::new(def_id.clone(), ElementKind::PartDefinition)
.with_name("Part1")
.with_owner(pkg_id.clone());
model.elements.insert(pkg_id.clone(), pkg);
model.elements.insert(def_id.clone(), def);
model.roots.push(pkg_id);
let result = decompile(&model);
assert!(result.text.contains("package MyPackage {"));
assert!(result.text.contains("part def Part1;"));
assert!(result.metadata.get_element("MyPackage::Part1").is_some());
}
#[test]
fn test_decompile_with_specialization() {
let mut model = Model::new();
let base = Element::new("base-1", ElementKind::PartDefinition).with_name("Base");
model.elements.insert(base.id.clone(), base);
model.roots.push(ElementId::from("base-1"));
let derived = Element::new("derived-1", ElementKind::PartDefinition).with_name("Derived");
model.elements.insert(derived.id.clone(), derived);
model.roots.push(ElementId::from("derived-1"));
let rel = Relationship::new(
"rel-1",
RelationshipKind::Specialization,
"derived-1",
"base-1",
);
model.relationships.push(rel);
let result = decompile(&model);
assert!(result.text.contains("part def Derived :> Base;"));
}
#[test]
fn test_decompile_part_usage_with_typing() {
let mut model = Model::new();
let pkg_id = ElementId::from("pkg-1");
let def_id = ElementId::from("def-1");
let usage_id = ElementId::from("usage-1");
let def = Element::new(def_id.clone(), ElementKind::PartDefinition).with_name("Engine");
let usage = Element::new(usage_id.clone(), ElementKind::PartUsage)
.with_name("engine")
.with_owner(pkg_id.clone());
let mut pkg = Element::new(pkg_id.clone(), ElementKind::Package).with_name("Car");
pkg.owned_elements.push(def_id.clone());
pkg.owned_elements.push(usage_id.clone());
model.elements.insert(pkg_id.clone(), pkg);
model.elements.insert(def_id.clone(), def);
model.elements.insert(usage_id.clone(), usage);
model.roots.push(pkg_id);
let rel = Relationship::new("rel-1", RelationshipKind::FeatureTyping, "usage-1", "def-1");
model.relationships.push(rel);
let result = decompile(&model);
assert!(result.text.contains("part engine : Engine;"));
}
#[test]
fn test_decompile_with_documentation() {
let mut model = Model::new();
let mut def = Element::new("def-1", ElementKind::PartDefinition).with_name("Documented");
def.documentation = Some("This is a documented element.".into());
def.owned_elements.push(ElementId::from("dummy"));
let child =
Element::new("dummy", ElementKind::Comment).with_owner(ElementId::from("def-1"));
model.elements.insert(child.id.clone(), child);
model.elements.insert(def.id.clone(), def);
model.roots.push(ElementId::from("def-1"));
let result = decompile(&model);
assert!(
result
.text
.contains("doc /* This is a documented element. */")
);
}
#[test]
fn test_decompile_library_package() {
let mut model = Model::new();
let pkg = Element::new("pkg-1", ElementKind::LibraryPackage).with_name("MyLibrary");
model.elements.insert(pkg.id.clone(), pkg);
model.roots.push(ElementId::from("pkg-1"));
let result = decompile(&model);
assert!(result.text.contains("library package MyLibrary {"));
}
#[test]
fn test_metadata_preserves_source_info() {
let model = Model::new();
let source = SourceInfo::from_path("/path/to/model.xmi").with_format("xmi");
let result = decompile_with_source(&model, source);
assert_eq!(
result.metadata.source.path.as_deref(),
Some("/path/to/model.xmi")
);
assert_eq!(result.metadata.source.format.as_deref(), Some("xmi"));
}
#[test]
fn test_decompile_private_visibility() {
let mut model = Model::new();
let mut def = Element::new("def-1", ElementKind::PartDefinition).with_name("PrivatePart");
def.visibility = Visibility::Private;
model.elements.insert(def.id.clone(), def);
model.roots.push(ElementId::from("def-1"));
let result = decompile(&model);
assert!(result.text.contains("private part def PrivatePart;"));
}
#[test]
fn test_decompile_namespace_import() {
let mut model = Model::new();
let target_pkg = Element::new("target-1", ElementKind::Package).with_name("TargetPackage");
model.elements.insert(target_pkg.id.clone(), target_pkg);
model.roots.push(ElementId::from("target-1"));
let mut pkg = Element::new("pkg-1", ElementKind::Package).with_name("MyPackage");
pkg.owned_elements.push(ElementId::from("dummy"));
let child = Element::new("dummy", ElementKind::PartDefinition)
.with_name("Dummy")
.with_owner(ElementId::from("pkg-1"));
model.elements.insert(child.id.clone(), child);
model.elements.insert(pkg.id.clone(), pkg);
model.roots.push(ElementId::from("pkg-1"));
let mut rel = Relationship::new(
"import-1",
RelationshipKind::NamespaceImport,
"pkg-1",
"target-1",
);
rel.owner = Some(ElementId::from("pkg-1"));
model.relationships.push(rel);
let result = decompile(&model);
assert!(result.text.contains("import TargetPackage::*;"));
}
#[test]
fn test_decompile_with_subsetting() {
let mut model = Model::new();
let base = Element::new("base-1", ElementKind::PartUsage).with_name("basePart");
model.elements.insert(base.id.clone(), base);
model.roots.push(ElementId::from("base-1"));
let derived = Element::new("derived-1", ElementKind::PartUsage).with_name("derivedPart");
model.elements.insert(derived.id.clone(), derived);
model.roots.push(ElementId::from("derived-1"));
let rel = Relationship::new("rel-1", RelationshipKind::Subsetting, "derived-1", "base-1");
model.relationships.push(rel);
let result = decompile(&model);
assert!(result.text.contains("part derivedPart subsets basePart;"));
}
#[test]
fn test_decompile_with_redefinition() {
let mut model = Model::new();
let original = Element::new("orig-1", ElementKind::PartUsage).with_name("originalPart");
model.elements.insert(original.id.clone(), original);
model.roots.push(ElementId::from("orig-1"));
let redefining = Element::new("redef-1", ElementKind::PartUsage).with_name("redefPart");
model.elements.insert(redefining.id.clone(), redefining);
model.roots.push(ElementId::from("redef-1"));
let rel = Relationship::new("rel-1", RelationshipKind::Redefinition, "redef-1", "orig-1");
model.relationships.push(rel);
let result = decompile(&model);
assert!(
result
.text
.contains("part redefPart redefines originalPart;")
);
}
#[test]
fn test_decompile_usage_with_typing_and_subsetting() {
let mut model = Model::new();
let type_def = Element::new("type-1", ElementKind::PartDefinition).with_name("Engine");
model.elements.insert(type_def.id.clone(), type_def);
model.roots.push(ElementId::from("type-1"));
let base = Element::new("base-1", ElementKind::PartUsage).with_name("basePart");
model.elements.insert(base.id.clone(), base);
model.roots.push(ElementId::from("base-1"));
let feature = Element::new("feat-1", ElementKind::PartUsage).with_name("myEngine");
model.elements.insert(feature.id.clone(), feature);
model.roots.push(ElementId::from("feat-1"));
model.relationships.push(Relationship::new(
"rel-1",
RelationshipKind::FeatureTyping,
"feat-1",
"type-1",
));
model.relationships.push(Relationship::new(
"rel-2",
RelationshipKind::Subsetting,
"feat-1",
"base-1",
));
let result = decompile(&model);
assert!(
result
.text
.contains("part myEngine : Engine subsets basePart;")
);
}
#[test]
fn test_decompile_xmi_roundtrip() {
use crate::interchange::{ModelFormat, Xmi};
use crate::syntax::sysml::parser::parse_content;
use std::path::Path;
let xmi_content = br#"<?xml version="1.0" encoding="UTF-8"?>
<xmi:XMI xmlns:xmi="http://www.omg.org/spec/XMI/20131001"
xmlns:sysml="http://www.omg.org/spec/SysML/20230201">
<sysml:Package xmi:id="pkg1" name="VehicleModel">
<ownedMember>
<sysml:PartDefinition xmi:id="pd1" name="Vehicle"/>
</ownedMember>
<ownedMember>
<sysml:PartDefinition xmi:id="pd2" name="Car"/>
</ownedMember>
</sysml:Package>
</xmi:XMI>"#;
let model = Xmi.read(xmi_content).expect("Failed to read XMI");
assert_eq!(model.element_count(), 3);
let result = decompile(&model);
assert!(
result.text.contains("package VehicleModel"),
"Missing package: {}",
result.text
);
assert!(
result.text.contains("part def Vehicle"),
"Missing Vehicle: {}",
result.text
);
assert!(
result.text.contains("part def Car"),
"Missing Car: {}",
result.text
);
let parse_result = parse_content(&result.text, Path::new("generated.sysml"));
assert!(
parse_result.is_ok(),
"Parse failed: {:?}",
parse_result.err()
);
assert!(result.metadata.get_element("VehicleModel").is_some());
assert!(
result
.metadata
.get_element("VehicleModel::Vehicle")
.is_some()
);
assert!(result.metadata.get_element("VehicleModel::Car").is_some());
let pkg_meta = result.metadata.get_element("VehicleModel").unwrap();
assert_eq!(pkg_meta.original_id.as_deref(), Some("pkg1"));
}
#[test]
fn test_metadata_keyed_by_qualified_name() {
let mut model = Model::new();
let mut pkg = Element::new("pkg-1", ElementKind::Package).with_name("TestPackage");
pkg.owned_elements.push(ElementId::from("def-1"));
model.add_element(pkg);
let mut def = Element::new("def-1", ElementKind::PartDefinition)
.with_name("OuterDef")
.with_owner("pkg-1");
def.owned_elements.push(ElementId::from("usage-1"));
model.add_element(def);
model.add_element(
Element::new("usage-1", ElementKind::PartUsage)
.with_name("innerPart")
.with_owner("def-1"),
);
let result = decompile(&model);
assert!(
result.metadata.get_element("TestPackage").is_some(),
"Should have metadata for TestPackage"
);
assert!(
result
.metadata
.get_element("TestPackage::OuterDef")
.is_some(),
"Should have metadata for TestPackage::OuterDef, got keys: {:?}",
result.metadata.elements.keys().collect::<Vec<_>>()
);
assert!(
result
.metadata
.get_element("TestPackage::OuterDef::innerPart")
.is_some(),
"Should have metadata for TestPackage::OuterDef::innerPart"
);
assert_eq!(
result
.metadata
.get_element("TestPackage")
.unwrap()
.original_id
.as_deref(),
Some("pkg-1")
);
assert_eq!(
result
.metadata
.get_element("TestPackage::OuterDef")
.unwrap()
.original_id
.as_deref(),
Some("def-1")
);
assert_eq!(
result
.metadata
.get_element("TestPackage::OuterDef::innerPart")
.unwrap()
.original_id
.as_deref(),
Some("usage-1")
);
}
}