blvm_sdk/composition/
validation.rs1use crate::composition::registry::ModuleRegistry;
6use crate::composition::types::*;
7
8pub 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 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 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 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 match registry.resolve_dependencies(&module_names) {
52 Ok(resolved) => {
53 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 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 let valid = errors.is_empty();
90 Ok(ValidationResult {
91 valid,
92 errors,
93 warnings,
94 dependencies,
95 })
96}