1use alloc::boxed::Box;
11use alloc::collections::BTreeMap;
12use alloc::string::String;
13use alloc::sync::Arc;
14use std::sync::Mutex;
15
16use crate::cif::{CifError, ComponentExecutor};
17use crate::port::PortRegistry;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
21pub enum ContainerType {
22 Session,
24 Service,
26 Process,
28 Entity,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub enum LifecycleState {
35 Created,
37 Configured,
39 Active,
41 Passive,
43 Removed,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum ContainerError {
50 InstanceNotFound(String),
52 InvalidState {
54 current: LifecycleState,
56 operation: String,
58 },
59 Cif(CifError),
61}
62
63impl From<CifError> for ContainerError {
64 fn from(e: CifError) -> Self {
65 Self::Cif(e)
66 }
67}
68
69struct InstanceEntry {
70 state: LifecycleState,
71 executor: Box<dyn ComponentExecutor>,
72}
73
74pub struct Container {
76 container_type: ContainerType,
77 instances: Mutex<BTreeMap<String, InstanceEntry>>,
78 pub ports: Arc<PortRegistry>,
80}
81
82impl core::fmt::Debug for Container {
83 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
84 let n = self.instances.lock().ok().map(|g| g.len()).unwrap_or(0);
85 f.debug_struct("Container")
86 .field("type", &self.container_type)
87 .field("instances", &n)
88 .finish()
89 }
90}
91
92impl Container {
93 #[must_use]
95 pub fn new(container_type: ContainerType) -> Self {
96 Self {
97 container_type,
98 instances: Mutex::new(BTreeMap::new()),
99 ports: Arc::new(PortRegistry::new()),
100 }
101 }
102
103 #[must_use]
105 pub fn container_type(&self) -> ContainerType {
106 self.container_type
107 }
108
109 pub fn install_component(
115 &self,
116 instance_id: String,
117 mut executor: Box<dyn ComponentExecutor>,
118 context: Box<dyn crate::context::ComponentContext>,
119 ) -> Result<(), ContainerError> {
120 executor.set_context(context);
121 let mut g = self.instances.lock().map_err(|_| {
122 ContainerError::Cif(CifError::CcmException("instances mutex poisoned".into()))
123 })?;
124 g.insert(
125 instance_id,
126 InstanceEntry {
127 state: LifecycleState::Configured,
128 executor,
129 },
130 );
131 Ok(())
132 }
133
134 pub fn activate(&self, instance_id: &str) -> Result<(), ContainerError> {
139 self.transition(instance_id, "ccm_activate", LifecycleState::Active, |e| {
140 e.ccm_activate()
141 })
142 }
143
144 pub fn passivate(&self, instance_id: &str) -> Result<(), ContainerError> {
149 self.transition(instance_id, "ccm_passivate", LifecycleState::Passive, |e| {
150 e.ccm_passivate()
151 })
152 }
153
154 pub fn remove(&self, instance_id: &str) -> Result<(), ContainerError> {
159 self.transition(instance_id, "ccm_remove", LifecycleState::Removed, |e| {
160 e.ccm_remove()
161 })?;
162 if let Ok(mut g) = self.instances.lock() {
163 g.remove(instance_id);
164 }
165 Ok(())
166 }
167
168 #[must_use]
170 pub fn instance_count(&self) -> usize {
171 self.instances.lock().map(|g| g.len()).unwrap_or(0)
172 }
173
174 #[must_use]
176 pub fn state_of(&self, instance_id: &str) -> Option<LifecycleState> {
177 self.instances
178 .lock()
179 .ok()
180 .and_then(|g| g.get(instance_id).map(|e| e.state))
181 }
182
183 fn transition<F>(
184 &self,
185 instance_id: &str,
186 op: &str,
187 target: LifecycleState,
188 f: F,
189 ) -> Result<(), ContainerError>
190 where
191 F: FnOnce(&mut Box<dyn ComponentExecutor>) -> Result<(), CifError>,
192 {
193 let mut g = self.instances.lock().map_err(|_| {
194 ContainerError::Cif(CifError::CcmException("instances mutex poisoned".into()))
195 })?;
196 let entry = g
197 .get_mut(instance_id)
198 .ok_or_else(|| ContainerError::InstanceNotFound(instance_id.to_string()))?;
199 let valid = matches!(
206 (entry.state, target),
207 (LifecycleState::Configured, LifecycleState::Active)
208 | (LifecycleState::Active, LifecycleState::Passive)
209 | (LifecycleState::Passive, LifecycleState::Active)
210 | (LifecycleState::Active, LifecycleState::Removed)
211 | (LifecycleState::Passive, LifecycleState::Removed)
212 | (LifecycleState::Configured, LifecycleState::Removed)
213 );
214 if !valid {
215 return Err(ContainerError::InvalidState {
216 current: entry.state,
217 operation: op.to_string(),
218 });
219 }
220 f(&mut entry.executor)?;
221 entry.state = target;
222 Ok(())
223 }
224}
225
226#[cfg(test)]
227#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
228mod tests {
229 use super::*;
230 use crate::context::ComponentContext;
231 use core::sync::atomic::{AtomicUsize, Ordering};
232
233 struct AnonContext;
234 impl ComponentContext for AnonContext {
235 fn get_caller_principal(&self) -> Option<alloc::vec::Vec<u8>> {
236 None
237 }
238 }
239
240 struct CountingExecutor {
241 ctx_set: AtomicUsize,
242 activations: AtomicUsize,
243 passivations: AtomicUsize,
244 removals: AtomicUsize,
245 }
246
247 impl ComponentExecutor for CountingExecutor {
248 fn set_context(&mut self, _: Box<dyn ComponentContext>) {
249 self.ctx_set.fetch_add(1, Ordering::Relaxed);
250 }
251 fn ccm_activate(&mut self) -> Result<(), CifError> {
252 self.activations.fetch_add(1, Ordering::Relaxed);
253 Ok(())
254 }
255 fn ccm_passivate(&mut self) -> Result<(), CifError> {
256 self.passivations.fetch_add(1, Ordering::Relaxed);
257 Ok(())
258 }
259 fn ccm_remove(&mut self) -> Result<(), CifError> {
260 self.removals.fetch_add(1, Ordering::Relaxed);
261 Ok(())
262 }
263 }
264
265 fn fresh_executor() -> Box<dyn ComponentExecutor> {
266 Box::new(CountingExecutor {
267 ctx_set: AtomicUsize::new(0),
268 activations: AtomicUsize::new(0),
269 passivations: AtomicUsize::new(0),
270 removals: AtomicUsize::new(0),
271 })
272 }
273
274 #[test]
275 fn install_and_full_lifecycle_round_trip() {
276 let c = Container::new(ContainerType::Session);
277 c.install_component("inst-1".into(), fresh_executor(), Box::new(AnonContext))
278 .unwrap();
279 assert_eq!(c.state_of("inst-1"), Some(LifecycleState::Configured));
280 c.activate("inst-1").unwrap();
281 assert_eq!(c.state_of("inst-1"), Some(LifecycleState::Active));
282 c.passivate("inst-1").unwrap();
283 assert_eq!(c.state_of("inst-1"), Some(LifecycleState::Passive));
284 c.activate("inst-1").unwrap(); assert_eq!(c.state_of("inst-1"), Some(LifecycleState::Active));
286 c.remove("inst-1").unwrap();
287 assert_eq!(c.state_of("inst-1"), None); }
289
290 #[test]
291 fn passivate_in_configured_state_invalid() {
292 let c = Container::new(ContainerType::Session);
293 c.install_component("inst-1".into(), fresh_executor(), Box::new(AnonContext))
294 .unwrap();
295 let err = c.passivate("inst-1").unwrap_err();
296 assert!(matches!(err, ContainerError::InvalidState { .. }));
297 }
298
299 #[test]
300 fn unknown_instance_returns_not_found() {
301 let c = Container::new(ContainerType::Session);
302 let err = c.activate("inst-x").unwrap_err();
303 assert!(matches!(err, ContainerError::InstanceNotFound(_)));
304 }
305
306 #[test]
307 fn instance_count_tracks_install_and_remove() {
308 let c = Container::new(ContainerType::Service);
309 c.install_component("a".into(), fresh_executor(), Box::new(AnonContext))
310 .unwrap();
311 c.install_component("b".into(), fresh_executor(), Box::new(AnonContext))
312 .unwrap();
313 assert_eq!(c.instance_count(), 2);
314 c.activate("a").unwrap();
315 c.remove("a").unwrap();
316 assert_eq!(c.instance_count(), 1);
317 }
318}