Skip to main content

blvm_sdk/composition/
validation.rs

1//! Composition Validation
2//!
3//! Validates module compositions for conflicts, dependencies, and capabilities.
4
5use crate::composition::registry::ModuleRegistry;
6use crate::composition::types::*;
7
8/// Validate a node composition specification
9pub fn validate_composition(
10    spec: &NodeSpec,
11    registry: &ModuleRegistry,
12) -> Result<ValidationResult> {
13    let mut errors = Vec::new();
14    let warnings = Vec::new();
15    let mut dependencies = Vec::new();
16
17    // Resolve all module names
18    let module_names: Vec<String> = spec
19        .modules
20        .iter()
21        .filter(|m| m.enabled)
22        .map(|m| m.name.clone())
23        .collect();
24
25    // Check all modules exist
26    for module_spec in &spec.modules {
27        if !module_spec.enabled {
28            continue;
29        }
30
31        match registry.get_module(&module_spec.name, module_spec.version.as_deref()) {
32            Ok(info) => {
33                // Capability validation: required deps must be in composition
34                for dep_name in info.dependencies.keys() {
35                    if !module_names.contains(dep_name) {
36                        errors.push(format!(
37                            "Module '{}' requires '{}' which is not in composition",
38                            module_spec.name, dep_name
39                        ));
40                    }
41                }
42                dependencies.push(info);
43            }
44            Err(e) => {
45                errors.push(format!("Module '{}' not found: {}", module_spec.name, e));
46            }
47        }
48    }
49
50    // Resolve dependencies
51    match registry.resolve_dependencies(&module_names) {
52        Ok(resolved) => {
53            // Check for missing dependencies
54            for resolved_module in &resolved {
55                if !dependencies.iter().any(|d| d.name == resolved_module.name) {
56                    dependencies.push(resolved_module.clone());
57                }
58            }
59        }
60        Err(e) => {
61            errors.push(format!("Dependency resolution failed: {e}"));
62        }
63    }
64
65    // Check for capability conflicts: two modules providing same capability
66    let mut capability_providers: std::collections::HashMap<String, Vec<String>> =
67        std::collections::HashMap::new();
68    for dep in &dependencies {
69        for cap in &dep.capabilities {
70            capability_providers
71                .entry(cap.clone())
72                .or_default()
73                .push(dep.name.clone());
74        }
75    }
76    for (cap, providers) in &capability_providers {
77        if providers.len() > 1 {
78            errors.push(format!(
79                "Capability '{}' provided by multiple modules: {}",
80                cap,
81                providers.join(", ")
82            ));
83        }
84    }
85
86    // Check for circular dependencies
87    // (Already handled by dependency resolution, but double-check here)
88
89    let valid = errors.is_empty();
90    Ok(ValidationResult {
91        valid,
92        errors,
93        warnings,
94        dependencies,
95    })
96}