1use alloc::collections::BTreeMap;
11use alloc::string::String;
12use alloc::vec::Vec;
13
14#[derive(Debug, Clone, PartialEq, Eq, Default)]
20pub struct ImplementationDescription {
21 pub label: String,
23 pub uuid: String,
25 pub artifacts: Vec<String>,
27 pub realizes: String,
30 pub exec_params: BTreeMap<String, String>,
32 pub depends_on: Vec<ImplementationDependency>,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Default)]
40pub struct ImplementationDependency {
41 pub name: String,
43 pub min_version: String,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Default)]
52pub struct PackagedComponentImplementation {
53 pub component_id: String,
55 pub implementations: Vec<ImplementationDescription>,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Default)]
65pub struct ComponentPackageDescription {
66 pub label: String,
68 pub uuid: String,
70 pub component_id: String,
72 pub implementations: Vec<ImplementationDescription>,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Default)]
81pub struct PackageConfiguration {
82 pub label: String,
84 pub uuid: String,
86 pub package: ComponentPackageDescription,
88 pub selected_implementation: usize,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Default)]
96pub struct InstanceDeploymentDescription {
97 pub name: String,
99 pub implementation: String,
101 pub node: String,
103 pub config_props: BTreeMap<String, String>,
105 pub co_locate_with: Vec<String>,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Default)]
113pub struct DeploymentPlan {
114 pub label: String,
116 pub uuid: String,
118 pub realizes: String,
120 pub implementations: Vec<ImplementationDescription>,
122 pub instances: Vec<InstanceDeploymentDescription>,
124 pub connections: Vec<PlanConnection>,
126}
127
128#[derive(Debug, Clone, PartialEq, Eq, Default)]
131pub struct PlanConnection {
132 pub name: String,
134 pub source_instance: String,
136 pub source_port: String,
138 pub target_instance: String,
140 pub target_port: String,
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
146pub enum PlanError {
147 UnknownImplementation(String),
150 UnknownInstance(String),
153 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 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}