Skip to main content

zerodds_ami4ccm/
deployment.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Deployment-Support — Spec §7.8.
5//!
6//! Spec §7.8 (S. 14-15): "At runtime for the AMI4CCM connector an
7//! AMI4CCM Connector fragment has to be deployed by the D&C
8//! infrastructure. [...] The client component and fragment are
9//! required to be deployed within the same process."
10//!
11//! Wir produzieren D&C-Deployment-Fragmente fuer den AMI4CCM-Connector
12//! gemaess OMG D&C 4.0 (`formal/2006-04-02`):
13//!
14//! * **IDD** (Implementation Description Descriptor) — beschreibt das
15//!   Connector-Artifact (.so/.dll) und die Component-Implementation.
16//! * **CDP** (Component Deployment Plan) — Plan-Fragment fuer den
17//!   Connector als zusaetzliche Instance neben der Client-Component.
18//! * **Co-Location-Constraint** — Spec §7.8: Client und Connector
19//!   muessen im selben Process laufen.
20
21use alloc::format;
22use alloc::string::String;
23use alloc::vec;
24use alloc::vec::Vec;
25
26/// Connector-Implementation-Beschreibung — Eingabe fuer die Plan-
27/// Generierung.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct ConnectorImplementation {
30    /// Name des Original-Interfaces (z.B. `StockManager`).
31    pub original_interface: String,
32    /// Name der Client-Component-Instance (Spec §7.8: muss
33    /// co-located sein).
34    pub client_instance: String,
35    /// Implementation-Artifact-Path (Spec D&C §7.6 ImplementationArtifact).
36    pub artifact_path: String,
37}
38
39/// IDD — Implementation Description Descriptor (D&C §6.4).
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct ImplementationDescriptor {
42    /// `label` — eindeutige Identifizierung.
43    pub label: String,
44    /// `UUID` der Implementation.
45    pub uuid: String,
46    /// Liste von Artifact-Paths.
47    pub artifacts: Vec<String>,
48    /// Component-Repository-ID, die diese Implementation realisiert.
49    pub realizes: String,
50}
51
52impl ImplementationDescriptor {
53    /// Erzeugt eine IDD fuer einen AMI4CCM-Connector. Spec §7.8 +
54    /// D&C §6.4.
55    #[must_use]
56    pub fn for_connector(impl_: &ConnectorImplementation) -> Self {
57        let connector_name = format!("AMI4CCM_{}_Connector", impl_.original_interface);
58        Self {
59            label: format!("{connector_name}_Impl"),
60            uuid: format!(
61                "ami4ccm-{}-impl-uuid",
62                impl_.original_interface.to_lowercase()
63            ),
64            artifacts: vec![impl_.artifact_path.clone()],
65            realizes: format!("IDL:omg.org/CCM_AMI/{connector_name}:1.0"),
66        }
67    }
68}
69
70/// CDP-Fragment — eine Plan-Component-Instance (D&C §7.4).
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct PlanInstance {
73    /// `name` — Instance-Name innerhalb des Plans.
74    pub name: String,
75    /// Reference auf eine ImplementationDescription.
76    pub implementation: String,
77    /// Co-Location-Constraint (Spec §7.8: dasselbe ProcessId).
78    pub co_locate_with: Option<String>,
79}
80
81/// Deployment-Plan-Fragment fuer den AMI4CCM-Connector neben einer
82/// existierenden Client-Component.
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct ConnectorPlanFragment {
85    /// IDD fuer das Connector-Artifact.
86    pub implementation: ImplementationDescriptor,
87    /// PlanInstance fuer den Connector — co-located mit dem Client.
88    pub instance: PlanInstance,
89}
90
91impl ConnectorPlanFragment {
92    /// Erzeugt das komplette Plan-Fragment (Spec §7.8 + D&C §7.4).
93    #[must_use]
94    pub fn build(impl_: &ConnectorImplementation) -> Self {
95        let implementation = ImplementationDescriptor::for_connector(impl_);
96        let instance = PlanInstance {
97            name: format!("{}_AMI4CCM_Connector", impl_.client_instance),
98            implementation: implementation.label.clone(),
99            co_locate_with: Some(impl_.client_instance.clone()),
100        };
101        Self {
102            implementation,
103            instance,
104        }
105    }
106
107    /// Spec §7.8 — Client und Fragment muessen im selben Process sein.
108    #[must_use]
109    pub fn is_co_located(&self) -> bool {
110        self.instance.co_locate_with.is_some()
111    }
112
113    /// Serialisiert das Plan-Fragment zu D&C-XML (D&C §10).
114    /// Minimal-Form mit `<implementation>` und `<instance>`.
115    #[must_use]
116    pub fn to_dnc_xml(&self) -> String {
117        let mut xml = String::new();
118        xml.push_str("<deploymentPlan>\n");
119        xml.push_str("  <implementation>\n");
120        xml.push_str(&format!("    <name>{}</name>\n", self.implementation.label));
121        xml.push_str(&format!("    <UUID>{}</UUID>\n", self.implementation.uuid));
122        for art in &self.implementation.artifacts {
123            xml.push_str(&format!("    <artifact>{art}</artifact>\n"));
124        }
125        xml.push_str(&format!(
126            "    <realizes>{}</realizes>\n",
127            self.implementation.realizes
128        ));
129        xml.push_str("  </implementation>\n");
130        xml.push_str("  <instance>\n");
131        xml.push_str(&format!("    <name>{}</name>\n", self.instance.name));
132        xml.push_str(&format!(
133            "    <implementation>{}</implementation>\n",
134            self.instance.implementation
135        ));
136        if let Some(ref c) = self.instance.co_locate_with {
137            xml.push_str(&format!("    <coLocateWith>{c}</coLocateWith>\n"));
138        }
139        xml.push_str("  </instance>\n");
140        xml.push_str("</deploymentPlan>\n");
141        xml
142    }
143}
144
145#[cfg(test)]
146#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
147mod tests {
148    use super::*;
149
150    fn sample_impl() -> ConnectorImplementation {
151        ConnectorImplementation {
152            original_interface: "StockManager".to_string(),
153            client_instance: "TraderClient".to_string(),
154            artifact_path: "lib/ami4ccm_stockmanager.so".to_string(),
155        }
156    }
157
158    #[test]
159    fn idd_realizes_correct_repository_id() {
160        let impl_ = sample_impl();
161        let idd = ImplementationDescriptor::for_connector(&impl_);
162        assert_eq!(
163            idd.realizes,
164            "IDL:omg.org/CCM_AMI/AMI4CCM_StockManager_Connector:1.0"
165        );
166        assert!(!idd.artifacts.is_empty());
167    }
168
169    #[test]
170    fn plan_fragment_is_co_located() {
171        let impl_ = sample_impl();
172        let frag = ConnectorPlanFragment::build(&impl_);
173        assert!(frag.is_co_located());
174        assert_eq!(
175            frag.instance.co_locate_with.as_deref(),
176            Some("TraderClient")
177        );
178    }
179
180    #[test]
181    fn plan_instance_name_combines_client_and_connector() {
182        let impl_ = sample_impl();
183        let frag = ConnectorPlanFragment::build(&impl_);
184        assert_eq!(frag.instance.name, "TraderClient_AMI4CCM_Connector");
185    }
186
187    #[test]
188    fn xml_roundtrip_includes_co_location() {
189        let impl_ = sample_impl();
190        let frag = ConnectorPlanFragment::build(&impl_);
191        let xml = frag.to_dnc_xml();
192        assert!(xml.contains("<deploymentPlan>"));
193        assert!(xml.contains("<coLocateWith>TraderClient</coLocateWith>"));
194        assert!(xml.contains("AMI4CCM_StockManager_Connector"));
195    }
196
197    #[test]
198    fn xml_lists_all_artifacts() {
199        let mut impl_ = sample_impl();
200        impl_.artifact_path = "lib/a.so".into();
201        let frag = ConnectorPlanFragment::build(&impl_);
202        let xml = frag.to_dnc_xml();
203        assert!(xml.contains("<artifact>lib/a.so</artifact>"));
204    }
205}