peat_protocol/composition/
engine.rs1use crate::composition::rules::{CompositionContext, CompositionResult, CompositionRule};
7use crate::models::capability::Capability;
8use crate::Result;
9use std::sync::Arc;
10use tracing::{debug, instrument};
11
12pub struct CompositionEngine {
14 rules: Vec<Arc<dyn CompositionRule>>,
16}
17
18impl CompositionEngine {
19 pub fn new() -> Self {
21 Self { rules: Vec::new() }
22 }
23
24 pub fn register_rule(&mut self, rule: Arc<dyn CompositionRule>) {
28 debug!("Registering composition rule: {}", rule.name());
29 self.rules.push(rule);
30 }
31
32 pub fn rule_count(&self) -> usize {
34 self.rules.len()
35 }
36
37 #[instrument(skip(self, capabilities, context))]
42 pub async fn compose(
43 &self,
44 capabilities: &[Capability],
45 context: &CompositionContext,
46 ) -> Result<Vec<CompositionResult>> {
47 debug!(
48 "Composing {} capabilities with {} rules",
49 capabilities.len(),
50 self.rules.len()
51 );
52
53 let mut all_results = Vec::new();
54
55 for rule in &self.rules {
56 if rule.applies_to(capabilities) {
57 debug!("Applying rule: {}", rule.name());
58 let result = rule.compose(capabilities, context).await?;
59
60 if result.has_compositions() {
61 debug!(
62 "Rule {} produced {} compositions",
63 rule.name(),
64 result.composed_capabilities.len()
65 );
66 all_results.push(result);
67 }
68 } else {
69 debug!("Rule {} does not apply", rule.name());
70 }
71 }
72
73 debug!("Total compositions: {}", all_results.len());
74 Ok(all_results)
75 }
76
77 pub async fn compose_all(
82 &self,
83 capabilities: &[Capability],
84 context: &CompositionContext,
85 ) -> Result<Vec<Capability>> {
86 let results = self.compose(capabilities, context).await?;
87
88 let composed_capabilities: Vec<Capability> = results
89 .into_iter()
90 .flat_map(|r| r.composed_capabilities)
91 .collect();
92
93 Ok(composed_capabilities)
94 }
95}
96
97impl Default for CompositionEngine {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use crate::composition::rules::CompositionRule;
107 use crate::models::capability::{CapabilityExt, CapabilityType};
108 use async_trait::async_trait;
109
110 struct MockRule {
112 name: String,
113 should_apply: bool,
114 result_count: usize,
115 }
116
117 impl MockRule {
118 fn new(name: &str, should_apply: bool, result_count: usize) -> Self {
119 Self {
120 name: name.to_string(),
121 should_apply,
122 result_count,
123 }
124 }
125 }
126
127 #[async_trait]
128 impl CompositionRule for MockRule {
129 fn name(&self) -> &str {
130 &self.name
131 }
132
133 fn description(&self) -> &str {
134 "Mock rule for testing"
135 }
136
137 fn applies_to(&self, _capabilities: &[Capability]) -> bool {
138 self.should_apply
139 }
140
141 async fn compose(
142 &self,
143 _capabilities: &[Capability],
144 _context: &CompositionContext,
145 ) -> Result<CompositionResult> {
146 let composed: Vec<Capability> = (0..self.result_count)
147 .map(|i| {
148 Capability::new(
149 format!("composed_{}", i),
150 format!("Composed {}", i),
151 CapabilityType::Emergent,
152 0.8,
153 )
154 })
155 .collect();
156
157 Ok(CompositionResult::new(composed, 0.8))
158 }
159 }
160
161 #[test]
162 fn test_engine_creation() {
163 let engine = CompositionEngine::new();
164 assert_eq!(engine.rule_count(), 0);
165 }
166
167 #[test]
168 fn test_register_rule() {
169 let mut engine = CompositionEngine::new();
170 let rule = Arc::new(MockRule::new("test_rule", true, 1));
171
172 engine.register_rule(rule);
173 assert_eq!(engine.rule_count(), 1);
174 }
175
176 #[test]
177 fn test_register_multiple_rules() {
178 let mut engine = CompositionEngine::new();
179
180 engine.register_rule(Arc::new(MockRule::new("rule1", true, 1)));
181 engine.register_rule(Arc::new(MockRule::new("rule2", true, 1)));
182 engine.register_rule(Arc::new(MockRule::new("rule3", true, 1)));
183
184 assert_eq!(engine.rule_count(), 3);
185 }
186
187 #[tokio::test]
188 async fn test_compose_with_applicable_rule() {
189 let mut engine = CompositionEngine::new();
190 engine.register_rule(Arc::new(MockRule::new("applicable", true, 2)));
191
192 let capabilities = vec![Capability::new(
193 "sensor".to_string(),
194 "Sensor".to_string(),
195 CapabilityType::Sensor,
196 0.9,
197 )];
198
199 let context = CompositionContext::new(vec!["node1".to_string()]);
200 let results = engine.compose(&capabilities, &context).await.unwrap();
201
202 assert_eq!(results.len(), 1);
203 assert_eq!(results[0].composed_capabilities.len(), 2);
204 }
205
206 #[tokio::test]
207 async fn test_compose_with_non_applicable_rule() {
208 let mut engine = CompositionEngine::new();
209 engine.register_rule(Arc::new(MockRule::new("not_applicable", false, 2)));
210
211 let capabilities = vec![Capability::new(
212 "sensor".to_string(),
213 "Sensor".to_string(),
214 CapabilityType::Sensor,
215 0.9,
216 )];
217
218 let context = CompositionContext::new(vec!["node1".to_string()]);
219 let results = engine.compose(&capabilities, &context).await.unwrap();
220
221 assert_eq!(results.len(), 0);
223 }
224
225 #[tokio::test]
226 async fn test_compose_with_multiple_rules() {
227 let mut engine = CompositionEngine::new();
228 engine.register_rule(Arc::new(MockRule::new("rule1", true, 1)));
229 engine.register_rule(Arc::new(MockRule::new("rule2", true, 2)));
230 engine.register_rule(Arc::new(MockRule::new("rule3", false, 1))); let capabilities = vec![Capability::new(
233 "sensor".to_string(),
234 "Sensor".to_string(),
235 CapabilityType::Sensor,
236 0.9,
237 )];
238
239 let context = CompositionContext::new(vec!["node1".to_string()]);
240 let results = engine.compose(&capabilities, &context).await.unwrap();
241
242 assert_eq!(results.len(), 2);
244 assert_eq!(results[0].composed_capabilities.len(), 1);
245 assert_eq!(results[1].composed_capabilities.len(), 2);
246 }
247
248 #[tokio::test]
249 async fn test_compose_all() {
250 let mut engine = CompositionEngine::new();
251 engine.register_rule(Arc::new(MockRule::new("rule1", true, 2)));
252 engine.register_rule(Arc::new(MockRule::new("rule2", true, 3)));
253
254 let capabilities = vec![Capability::new(
255 "sensor".to_string(),
256 "Sensor".to_string(),
257 CapabilityType::Sensor,
258 0.9,
259 )];
260
261 let context = CompositionContext::new(vec!["node1".to_string()]);
262 let composed = engine.compose_all(&capabilities, &context).await.unwrap();
263
264 assert_eq!(composed.len(), 5);
266 }
267
268 #[tokio::test]
269 async fn test_compose_empty_capabilities() {
270 let mut engine = CompositionEngine::new();
271 engine.register_rule(Arc::new(MockRule::new("rule1", true, 1)));
272
273 let capabilities = vec![];
274 let context = CompositionContext::new(vec!["node1".to_string()]);
275 let results = engine.compose(&capabilities, &context).await.unwrap();
276
277 assert_eq!(results.len(), 1);
279 }
280}