Skip to main content

zerodds_corba_dnc/
plan.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Plan-Datenmodell — OMG D&C 4.0 §6 + §7.
5//!
6//! Wir modellieren die zentralen Plan-Strukturen ohne den vollen
7//! UML-Stack: nur das, was fuer eine deploybare CCM-Application
8//! relevant ist.
9
10use alloc::collections::BTreeMap;
11use alloc::string::String;
12use alloc::vec::Vec;
13
14/// `ImplementationDescription` (IDD) — D&C §6.4.
15///
16/// Beschreibt eine konkrete Component-Implementation: welches
17/// Artifact, welche Component-Repository-ID realisiert sie, und
18/// welche Konfigurations-Properties hat sie.
19#[derive(Debug, Clone, PartialEq, Eq, Default)]
20pub struct ImplementationDescription {
21    /// Eindeutiger Label (Spec D&C §6.4 `label`).
22    pub label: String,
23    /// UUID (Spec D&C §6.4 `UUID`).
24    pub uuid: String,
25    /// Liste von Artifact-Pfaden (`.so`/`.dll`/`.jar`).
26    pub artifacts: Vec<String>,
27    /// Repository-ID des Components, das diese Implementation
28    /// realisiert.
29    pub realizes: String,
30    /// Property-Map (D&C §6.4 `execParameter`).
31    pub exec_params: BTreeMap<String, String>,
32    /// Implementation-Dependencies (D&C §6.4 `dependsOn`).
33    pub depends_on: Vec<ImplementationDependency>,
34}
35
36/// Implementation-Dependency — D&C §6.5.
37///
38/// Spec §6.5: `name` + `version` + Required-Capabilities.
39#[derive(Debug, Clone, PartialEq, Eq, Default)]
40pub struct ImplementationDependency {
41    /// Required-Capability-Name (z.B. `Java_Runtime`).
42    pub name: String,
43    /// Mindestversion.
44    pub min_version: String,
45}
46
47/// `PackagedComponentImplementation` — D&C §6.6.
48///
49/// Verknuepft eine Component-Repository-ID mit den verfuegbaren
50/// Implementations.
51#[derive(Debug, Clone, PartialEq, Eq, Default)]
52pub struct PackagedComponentImplementation {
53    /// Component-Repository-ID.
54    pub component_id: String,
55    /// Liste der Implementations (z.B. eine fuer Linux, eine fuer
56    /// Windows).
57    pub implementations: Vec<ImplementationDescription>,
58}
59
60/// `ComponentPackageDescription` (CPD) — D&C §6.6.
61///
62/// Bundle-Beschreibung fuer eine Component plus alle ihre
63/// Implementations.
64#[derive(Debug, Clone, PartialEq, Eq, Default)]
65pub struct ComponentPackageDescription {
66    /// Eindeutiger Label.
67    pub label: String,
68    /// UUID.
69    pub uuid: String,
70    /// Repository-ID des Components.
71    pub component_id: String,
72    /// Implementations (mit Selection-Requirements).
73    pub implementations: Vec<ImplementationDescription>,
74}
75
76/// `PackageConfiguration` (PSD) — D&C §6.7.
77///
78/// Ein bereits selektiertes Package (Implementation-Selection
79/// abgeschlossen).
80#[derive(Debug, Clone, PartialEq, Eq, Default)]
81pub struct PackageConfiguration {
82    /// Eindeutiger Label.
83    pub label: String,
84    /// UUID.
85    pub uuid: String,
86    /// Selected Component-Package.
87    pub package: ComponentPackageDescription,
88    /// Selected Implementation (Index in package.implementations).
89    pub selected_implementation: usize,
90}
91
92/// `InstanceDeploymentDescription` (IDD-Instance) — D&C §7.5.
93///
94/// Eine konkrete Instance einer Component, mit Node-Assignment.
95#[derive(Debug, Clone, PartialEq, Eq, Default)]
96pub struct InstanceDeploymentDescription {
97    /// Instance-Name.
98    pub name: String,
99    /// Reference auf eine ImplementationDescription (label).
100    pub implementation: String,
101    /// Target-Node-Name.
102    pub node: String,
103    /// Configured-Properties (D&C §7.5 `configProperty`).
104    pub config_props: BTreeMap<String, String>,
105    /// Co-Location-Constraint (Spec D&C §7.7) — Liste anderer
106    /// Instance-Names, mit denen diese Instance im selben Process
107    /// laufen muss.
108    pub co_locate_with: Vec<String>,
109}
110
111/// `DeploymentPlan` (DPD) — D&C §7.4.
112#[derive(Debug, Clone, PartialEq, Eq, Default)]
113pub struct DeploymentPlan {
114    /// Eindeutiger Label.
115    pub label: String,
116    /// UUID.
117    pub uuid: String,
118    /// Ressourcen-Requirements (z.B. min RAM).
119    pub realizes: String,
120    /// Implementations (D&C §7.4 `implementation`).
121    pub implementations: Vec<ImplementationDescription>,
122    /// Instances (D&C §7.4 `instance`).
123    pub instances: Vec<InstanceDeploymentDescription>,
124    /// Connection-Definitions (D&C §7.6 `connection`).
125    pub connections: Vec<PlanConnection>,
126}
127
128/// Plan-Connection — verbindet Receptacle einer Instance an Facet
129/// einer anderen.
130#[derive(Debug, Clone, PartialEq, Eq, Default)]
131pub struct PlanConnection {
132    /// Connection-Name.
133    pub name: String,
134    /// Source-Instance.
135    pub source_instance: String,
136    /// Source-Port (Receptacle-Name).
137    pub source_port: String,
138    /// Target-Instance.
139    pub target_instance: String,
140    /// Target-Port (Facet-Name).
141    pub target_port: String,
142}
143
144/// Plan-Validation-Fehler.
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub enum PlanError {
147    /// Instance referenziert eine Implementation, die nicht im Plan
148    /// existiert.
149    UnknownImplementation(String),
150    /// Connection referenziert eine Instance, die nicht im Plan
151    /// existiert.
152    UnknownInstance(String),
153    /// Co-Location-Liste verweist auf eine Instance, die nicht im
154    /// Plan existiert.
155    UnknownCoLocation(String),
156}
157
158impl core::fmt::Display for PlanError {
159    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
160        match self {
161            Self::UnknownImplementation(s) => write!(f, "unknown implementation `{s}`"),
162            Self::UnknownInstance(s) => write!(f, "unknown instance `{s}`"),
163            Self::UnknownCoLocation(s) => write!(f, "unknown co-location `{s}`"),
164        }
165    }
166}
167
168#[cfg(feature = "std")]
169impl std::error::Error for PlanError {}
170
171impl DeploymentPlan {
172    /// Spec D&C §7.4 — Validation: alle Instance-Implementations,
173    /// Connection-Endpoints und Co-Locations muessen im Plan
174    /// existieren.
175    ///
176    /// # Errors
177    /// Siehe [`PlanError`].
178    pub fn validate(&self) -> Result<(), PlanError> {
179        for inst in &self.instances {
180            if !self
181                .implementations
182                .iter()
183                .any(|i| i.label == inst.implementation)
184            {
185                return Err(PlanError::UnknownImplementation(
186                    inst.implementation.clone(),
187                ));
188            }
189            for c in &inst.co_locate_with {
190                if !self.instances.iter().any(|i| &i.name == c) {
191                    return Err(PlanError::UnknownCoLocation(c.clone()));
192                }
193            }
194        }
195        for conn in &self.connections {
196            if !self
197                .instances
198                .iter()
199                .any(|i| i.name == conn.source_instance)
200            {
201                return Err(PlanError::UnknownInstance(conn.source_instance.clone()));
202            }
203            if !self
204                .instances
205                .iter()
206                .any(|i| i.name == conn.target_instance)
207            {
208                return Err(PlanError::UnknownInstance(conn.target_instance.clone()));
209            }
210        }
211        Ok(())
212    }
213}
214
215#[cfg(test)]
216#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
217mod tests {
218    use super::*;
219    use alloc::format;
220
221    fn sample_plan() -> DeploymentPlan {
222        DeploymentPlan {
223            label: "Plan1".into(),
224            uuid: "uuid1".into(),
225            realizes: "Plan1".into(),
226            implementations: alloc::vec![ImplementationDescription {
227                label: "EchoImpl".into(),
228                uuid: "echo-uuid".into(),
229                artifacts: alloc::vec!["lib/echo.so".into()],
230                realizes: "IDL:demo/Echo:1.0".into(),
231                exec_params: BTreeMap::new(),
232                depends_on: alloc::vec![],
233            }],
234            instances: alloc::vec![InstanceDeploymentDescription {
235                name: "echo1".into(),
236                implementation: "EchoImpl".into(),
237                node: "Node1".into(),
238                config_props: BTreeMap::new(),
239                co_locate_with: alloc::vec![],
240            }],
241            connections: alloc::vec![],
242        }
243    }
244
245    #[test]
246    fn valid_plan_passes_validation() {
247        let p = sample_plan();
248        assert!(p.validate().is_ok());
249    }
250
251    #[test]
252    fn instance_with_unknown_impl_fails() {
253        let mut p = sample_plan();
254        p.instances[0].implementation = "NoSuch".into();
255        assert_eq!(
256            p.validate(),
257            Err(PlanError::UnknownImplementation("NoSuch".into()))
258        );
259    }
260
261    #[test]
262    fn connection_with_unknown_instance_fails() {
263        let mut p = sample_plan();
264        p.connections.push(PlanConnection {
265            name: "c1".into(),
266            source_instance: "echo1".into(),
267            source_port: "p".into(),
268            target_instance: "ghost".into(),
269            target_port: "f".into(),
270        });
271        assert_eq!(
272            p.validate(),
273            Err(PlanError::UnknownInstance("ghost".into()))
274        );
275    }
276
277    #[test]
278    fn co_location_with_unknown_instance_fails() {
279        let mut p = sample_plan();
280        p.instances[0].co_locate_with.push("ghost".into());
281        assert_eq!(
282            p.validate(),
283            Err(PlanError::UnknownCoLocation("ghost".into()))
284        );
285    }
286
287    #[test]
288    fn plan_error_display() {
289        let e = PlanError::UnknownImplementation("foo".into());
290        assert_eq!(format!("{e}"), "unknown implementation `foo`");
291    }
292}