use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ImplementationDescription {
pub label: String,
pub uuid: String,
pub artifacts: Vec<String>,
pub realizes: String,
pub exec_params: BTreeMap<String, String>,
pub depends_on: Vec<ImplementationDependency>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ImplementationDependency {
pub name: String,
pub min_version: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct PackagedComponentImplementation {
pub component_id: String,
pub implementations: Vec<ImplementationDescription>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ComponentPackageDescription {
pub label: String,
pub uuid: String,
pub component_id: String,
pub implementations: Vec<ImplementationDescription>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct PackageConfiguration {
pub label: String,
pub uuid: String,
pub package: ComponentPackageDescription,
pub selected_implementation: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct InstanceDeploymentDescription {
pub name: String,
pub implementation: String,
pub node: String,
pub config_props: BTreeMap<String, String>,
pub co_locate_with: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct DeploymentPlan {
pub label: String,
pub uuid: String,
pub realizes: String,
pub implementations: Vec<ImplementationDescription>,
pub instances: Vec<InstanceDeploymentDescription>,
pub connections: Vec<PlanConnection>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct PlanConnection {
pub name: String,
pub source_instance: String,
pub source_port: String,
pub target_instance: String,
pub target_port: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PlanError {
UnknownImplementation(String),
UnknownInstance(String),
UnknownCoLocation(String),
}
impl core::fmt::Display for PlanError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::UnknownImplementation(s) => write!(f, "unknown implementation `{s}`"),
Self::UnknownInstance(s) => write!(f, "unknown instance `{s}`"),
Self::UnknownCoLocation(s) => write!(f, "unknown co-location `{s}`"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for PlanError {}
impl DeploymentPlan {
pub fn validate(&self) -> Result<(), PlanError> {
for inst in &self.instances {
if !self
.implementations
.iter()
.any(|i| i.label == inst.implementation)
{
return Err(PlanError::UnknownImplementation(
inst.implementation.clone(),
));
}
for c in &inst.co_locate_with {
if !self.instances.iter().any(|i| &i.name == c) {
return Err(PlanError::UnknownCoLocation(c.clone()));
}
}
}
for conn in &self.connections {
if !self
.instances
.iter()
.any(|i| i.name == conn.source_instance)
{
return Err(PlanError::UnknownInstance(conn.source_instance.clone()));
}
if !self
.instances
.iter()
.any(|i| i.name == conn.target_instance)
{
return Err(PlanError::UnknownInstance(conn.target_instance.clone()));
}
}
Ok(())
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
use alloc::format;
fn sample_plan() -> DeploymentPlan {
DeploymentPlan {
label: "Plan1".into(),
uuid: "uuid1".into(),
realizes: "Plan1".into(),
implementations: alloc::vec![ImplementationDescription {
label: "EchoImpl".into(),
uuid: "echo-uuid".into(),
artifacts: alloc::vec!["lib/echo.so".into()],
realizes: "IDL:demo/Echo:1.0".into(),
exec_params: BTreeMap::new(),
depends_on: alloc::vec![],
}],
instances: alloc::vec![InstanceDeploymentDescription {
name: "echo1".into(),
implementation: "EchoImpl".into(),
node: "Node1".into(),
config_props: BTreeMap::new(),
co_locate_with: alloc::vec![],
}],
connections: alloc::vec![],
}
}
#[test]
fn valid_plan_passes_validation() {
let p = sample_plan();
assert!(p.validate().is_ok());
}
#[test]
fn instance_with_unknown_impl_fails() {
let mut p = sample_plan();
p.instances[0].implementation = "NoSuch".into();
assert_eq!(
p.validate(),
Err(PlanError::UnknownImplementation("NoSuch".into()))
);
}
#[test]
fn connection_with_unknown_instance_fails() {
let mut p = sample_plan();
p.connections.push(PlanConnection {
name: "c1".into(),
source_instance: "echo1".into(),
source_port: "p".into(),
target_instance: "ghost".into(),
target_port: "f".into(),
});
assert_eq!(
p.validate(),
Err(PlanError::UnknownInstance("ghost".into()))
);
}
#[test]
fn co_location_with_unknown_instance_fails() {
let mut p = sample_plan();
p.instances[0].co_locate_with.push("ghost".into());
assert_eq!(
p.validate(),
Err(PlanError::UnknownCoLocation("ghost".into()))
);
}
#[test]
fn plan_error_display() {
let e = PlanError::UnknownImplementation("foo".into());
assert_eq!(format!("{e}"), "unknown implementation `foo`");
}
}