ferrous_di/
labeled_scopes.rs

1//! Hierarchical labeled scopes for workflow engines.
2//!
3//! This module provides labeled scope capabilities essential for n8n-style workflow
4//! engines where you need nested contexts (workflow → run → node) with proper
5//! hierarchical organization and cleanup.
6
7use std::sync::Arc;
8use std::collections::HashMap;
9use crate::ServiceProvider;
10
11/// A hierarchical scope with a label for context and tracing.
12///
13/// Essential for workflow engines where you need nested execution contexts
14/// like workflow → run → node, each with their own label for tracing.
15///
16/// # Examples
17///
18/// ```
19/// use ferrous_di::{ServiceCollection, ServiceProvider, LabeledScope, LabeledScopeExt};
20///
21/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
22/// let mut services = ServiceCollection::new();
23///
24/// let provider = services.build();
25/// 
26/// // Create workflow scope
27/// let workflow_scope = provider.create_labeled_scope("workflow_123");
28/// 
29/// // Create run scope within workflow
30/// let run_scope = workflow_scope.fork("run_456");
31/// 
32/// // Create node scope within run
33/// let node_scope = run_scope.fork("node_789");
34/// 
35/// assert_eq!(workflow_scope.label(), "workflow_123");
36/// assert_eq!(run_scope.label(), "run_456");
37/// assert_eq!(node_scope.label(), "node_789");
38/// 
39/// // Labels can be used for tracing, logging, cleanup, etc.
40/// println!("Executing in context: {}", node_scope.full_path());
41/// # Ok(())
42/// # }
43/// ```
44#[derive(Clone)]
45pub struct LabeledScope {
46    inner: Arc<LabeledScopeInner>,
47}
48
49struct LabeledScopeInner {
50    label: &'static str,
51    parent: Option<LabeledScope>,
52    scope: crate::provider::Scope,
53    depth: usize,
54}
55
56impl LabeledScope {
57    /// Creates a new labeled scope from a regular scope.
58    pub(crate) fn new(scope: crate::provider::Scope, label: &'static str) -> Self {
59        Self {
60            inner: Arc::new(LabeledScopeInner {
61                label,
62                parent: None,
63                scope,
64                depth: 0,
65            })
66        }
67    }
68
69    /// Creates a child scope with the given label.
70    ///
71    /// Perfect for hierarchical workflow execution (flow → run → node).
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use ferrous_di::{ServiceCollection, ServiceProvider, LabeledScopeExt};
77    ///
78    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
79    /// let mut services = ServiceCollection::new();
80    /// let provider = services.build();
81    /// 
82    /// let workflow_scope = provider.create_labeled_scope("workflow");
83    /// let run_scope = workflow_scope.fork("run_001");
84    /// let node_scope = run_scope.fork("transform_node");
85    /// 
86    /// assert_eq!(node_scope.depth(), 2);
87    /// assert_eq!(node_scope.full_path(), "workflow/run_001/transform_node");
88    /// # Ok(())
89    /// # }
90    /// ```
91    pub fn fork(&self, label: &'static str) -> Self {
92        let child_scope = self.inner.scope.create_child();
93        
94        Self {
95            inner: Arc::new(LabeledScopeInner {
96                label,
97                parent: Some(self.clone()),
98                scope: child_scope,
99                depth: self.inner.depth + 1,
100            })
101        }
102    }
103
104    /// Returns the label of this scope.
105    pub fn label(&self) -> &'static str {
106        self.inner.label
107    }
108
109    /// Returns the depth of this scope (0 for root, 1 for first child, etc.).
110    pub fn depth(&self) -> usize {
111        self.inner.depth
112    }
113
114    /// Returns the parent scope, if any.
115    pub fn parent(&self) -> Option<&LabeledScope> {
116        self.inner.parent.as_ref()
117    }
118
119    /// Returns the full hierarchical path (e.g., "workflow/run_001/node_123").
120    ///
121    /// Perfect for logging, tracing, and debugging workflow execution.
122    ///
123    /// # Examples
124    ///
125    /// ```
126    /// use ferrous_di::{ServiceCollection, ServiceProvider, LabeledScopeExt};
127    ///
128    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
129    /// let mut services = ServiceCollection::new();
130    /// let provider = services.build();
131    /// 
132    /// let scope = provider.create_labeled_scope("workflow")
133    ///     .fork("run_001")
134    ///     .fork("transform_node");
135    /// 
136    /// assert_eq!(scope.full_path(), "workflow/run_001/transform_node");
137    /// # Ok(())
138    /// # }
139    /// ```
140    pub fn full_path(&self) -> String {
141        let mut path_parts = Vec::new();
142        let mut current = Some(self);
143        
144        while let Some(scope) = current {
145            path_parts.push(scope.label());
146            current = scope.parent();
147        }
148        
149        path_parts.reverse();
150        path_parts.join("/")
151    }
152
153    /// Returns all ancestor labels from root to this scope.
154    ///
155    /// Useful for hierarchical context tracking in workflow engines.
156    ///
157    /// # Examples
158    ///
159    /// ```
160    /// use ferrous_di::{ServiceCollection, ServiceProvider, LabeledScopeExt};
161    ///
162    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
163    /// let mut services = ServiceCollection::new();
164    /// let provider = services.build();
165    /// 
166    /// let scope = provider.create_labeled_scope("workflow")
167    ///     .fork("run_001")
168    ///     .fork("transform");
169    /// 
170    /// let ancestors = scope.ancestors();
171    /// assert_eq!(ancestors, vec!["workflow", "run_001", "transform"]);
172    /// # Ok(())
173    /// # }
174    /// ```
175    pub fn ancestors(&self) -> Vec<&'static str> {
176        let mut ancestors = Vec::new();
177        let mut current = Some(self);
178        
179        while let Some(scope) = current {
180            ancestors.push(scope.label());
181            current = scope.parent();
182        }
183        
184        ancestors.reverse();
185        ancestors
186    }
187
188    /// Finds an ancestor scope with the given label.
189    ///
190    /// Perfect for workflow engines where you need to access parent contexts.
191    ///
192    /// # Examples
193    ///
194    /// ```
195    /// use ferrous_di::{ServiceCollection, ServiceProvider, LabeledScopeExt};
196    ///
197    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
198    /// let mut services = ServiceCollection::new();
199    /// let provider = services.build();
200    /// 
201    /// let workflow_scope = provider.create_labeled_scope("workflow_123");
202    /// let run_scope = workflow_scope.fork("run_456");
203    /// let node_scope = run_scope.fork("node_789");
204    /// 
205    /// let workflow = node_scope.find_ancestor("workflow_123").unwrap();
206    /// assert_eq!(workflow.label(), "workflow_123");
207    /// # Ok(())
208    /// # }
209    /// ```
210    pub fn find_ancestor(&self, label: &str) -> Option<&LabeledScope> {
211        let mut current = Some(self);
212        
213        while let Some(scope) = current {
214            if scope.label() == label {
215                return Some(scope);
216            }
217            current = scope.parent();
218        }
219        
220        None
221    }
222
223    /// Returns true if this scope is an ancestor of (or equal to) the other scope.
224    ///
225    /// Useful for validating scope hierarchies in workflow engines.
226    ///
227    /// # Examples
228    ///
229    /// ```
230    /// use ferrous_di::{ServiceCollection, ServiceProvider, LabeledScopeExt};
231    ///
232    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
233    /// let mut services = ServiceCollection::new();
234    /// let provider = services.build();
235    /// 
236    /// let workflow_scope = provider.create_labeled_scope("workflow");
237    /// let run_scope = workflow_scope.fork("run");
238    /// let node_scope = run_scope.fork("node");
239    /// 
240    /// assert!(workflow_scope.is_ancestor_of(&node_scope));
241    /// assert!(run_scope.is_ancestor_of(&node_scope));
242    /// assert!(!node_scope.is_ancestor_of(&workflow_scope));
243    /// # Ok(())
244    /// # }
245    /// ```
246    pub fn is_ancestor_of(&self, other: &LabeledScope) -> bool {
247        let mut current = Some(other);
248        
249        while let Some(scope) = current {
250            if std::ptr::eq(self.inner.as_ref(), scope.inner.as_ref()) {
251                return true;
252            }
253            current = scope.parent();
254        }
255        
256        false
257    }
258
259    /// Returns metadata about this scope for tracing and debugging.
260    ///
261    /// Essential for workflow engines that need rich context information.
262    ///
263    /// # Examples
264    ///
265    /// ```
266    /// use ferrous_di::{ServiceCollection, ServiceProvider, LabeledScopeExt};
267    ///
268    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
269    /// let mut services = ServiceCollection::new();
270    /// let provider = services.build();
271    /// 
272    /// let scope = provider.create_labeled_scope("workflow")
273    ///     .fork("run_001");
274    /// 
275    /// let metadata = scope.metadata();
276    /// assert_eq!(metadata.label, "run_001");
277    /// assert_eq!(metadata.depth, 1);
278    /// assert_eq!(metadata.full_path, "workflow/run_001");
279    /// assert_eq!(metadata.ancestors, vec!["workflow", "run_001"]);
280    /// # Ok(())
281    /// # }
282    /// ```
283    pub fn metadata(&self) -> ScopeMetadata {
284        ScopeMetadata {
285            label: self.label(),
286            depth: self.depth(),
287            full_path: self.full_path(),
288            ancestors: self.ancestors(),
289            parent_label: self.parent().map(|p| p.label()),
290        }
291    }
292
293    /// Access the underlying scope for service resolution.
294    ///
295    /// This provides access to all the DI functionality while maintaining
296    /// the labeled scope context.
297    pub fn as_scope(&self) -> &crate::provider::Scope {
298        &self.inner.scope
299    }
300}
301
302/// Metadata about a labeled scope for tracing and debugging.
303#[derive(Debug, Clone, PartialEq)]
304pub struct ScopeMetadata {
305    /// The label of this scope
306    pub label: &'static str,
307    /// The depth in the hierarchy (0 for root)
308    pub depth: usize,
309    /// The full hierarchical path
310    pub full_path: String,
311    /// All ancestor labels from root to this scope
312    pub ancestors: Vec<&'static str>,
313    /// The parent scope's label, if any
314    pub parent_label: Option<&'static str>,
315}
316
317/// Extension trait for ServiceProvider to create labeled scopes.
318pub trait LabeledScopeExt {
319    /// Creates a new labeled scope.
320    ///
321    /// This is the entry point for hierarchical workflow contexts.
322    ///
323    /// # Examples
324    ///
325    /// ```
326    /// use ferrous_di::{ServiceCollection, LabeledScopeExt};
327    ///
328    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
329    /// let mut services = ServiceCollection::new();
330    /// let provider = services.build();
331    /// 
332    /// let workflow_scope = provider.create_labeled_scope("workflow_123");
333    /// assert_eq!(workflow_scope.label(), "workflow_123");
334    /// assert_eq!(workflow_scope.depth(), 0);
335    /// # Ok(())
336    /// # }
337    /// ```
338    fn create_labeled_scope(&self, label: &'static str) -> LabeledScope;
339}
340
341impl LabeledScopeExt for ServiceProvider {
342    fn create_labeled_scope(&self, label: &'static str) -> LabeledScope {
343        let scope = self.create_scope();
344        LabeledScope::new(scope, label)
345    }
346}
347
348/// Helper for creating scoped context in workflow engines.
349///
350/// This provides a convenient way to pass labeled scope context
351/// through workflow execution without manual parameter threading.
352///
353/// # Examples
354///
355/// ```
356/// use ferrous_di::{ServiceCollection, LabeledScopeContext, LabeledScopeExt, Resolver};
357///
358/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
359/// let mut services = ServiceCollection::new();
360/// 
361/// // Register the scope context with a factory
362/// services.add_scoped_factory::<LabeledScopeContext, _>(|_| {
363///     // This would be populated by the labeled scope in practice
364///     panic!("Should be replaced by labeled scope")
365/// });
366/// 
367/// let provider = services.build();
368/// let scope = provider.create_labeled_scope("workflow");
369/// 
370/// // In practice, labeled scopes would register themselves in the context
371/// // assert_eq!(context.current_label(), "workflow");
372/// # Ok(())
373/// # }
374/// ```
375#[derive(Clone)]
376pub struct LabeledScopeContext {
377    scope: LabeledScope,
378}
379
380impl LabeledScopeContext {
381    /// Creates a new scope context.
382    pub fn new(scope: LabeledScope) -> Self {
383        Self { scope }
384    }
385
386    /// Returns the current scope's label.
387    pub fn current_label(&self) -> &'static str {
388        self.scope.label()
389    }
390
391    /// Returns the current scope's depth.
392    pub fn current_depth(&self) -> usize {
393        self.scope.depth()
394    }
395
396    /// Returns the full hierarchical path.
397    pub fn full_path(&self) -> String {
398        self.scope.full_path()
399    }
400
401    /// Returns the labeled scope for advanced operations.
402    pub fn labeled_scope(&self) -> &LabeledScope {
403        &self.scope
404    }
405
406    /// Returns metadata about the current scope.
407    pub fn metadata(&self) -> ScopeMetadata {
408        self.scope.metadata()
409    }
410}
411
412/// Registry for tracking active labeled scopes in workflow engines.
413///
414/// Essential for debugging, monitoring, and cleanup in complex workflows.
415///
416/// # Examples
417///
418/// ```
419/// use ferrous_di::{ServiceCollection, LabeledScopeRegistry, LabeledScopeExt, Resolver};
420/// use std::sync::Arc;
421///
422/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
423/// let mut services = ServiceCollection::new();
424/// services.add_singleton_factory::<LabeledScopeRegistry, _>(|_| LabeledScopeRegistry::new());
425///
426/// let provider = services.build();
427/// let registry = provider.get_required::<LabeledScopeRegistry>();
428/// 
429/// let scope = provider.create_labeled_scope("workflow_123");
430/// registry.register(&scope);
431/// 
432/// let active_scopes = registry.active_scopes();
433/// assert_eq!(active_scopes.len(), 1);
434/// assert_eq!(active_scopes[0].label(), "workflow_123");
435/// # Ok(())
436/// # }
437/// ```
438#[derive(Default)]
439pub struct LabeledScopeRegistry {
440    scopes: std::sync::Mutex<HashMap<String, LabeledScope>>,
441}
442
443impl LabeledScopeRegistry {
444    /// Creates a new scope registry.
445    pub fn new() -> Self {
446        Self::default()
447    }
448
449    /// Registers a labeled scope for tracking.
450    ///
451    /// Useful for monitoring active workflows and cleanup on shutdown.
452    pub fn register(&self, scope: &LabeledScope) {
453        let mut scopes = self.scopes.lock().unwrap();
454        scopes.insert(scope.full_path(), scope.clone());
455    }
456
457    /// Unregisters a labeled scope.
458    ///
459    /// Should be called when workflow execution completes.
460    pub fn unregister(&self, scope: &LabeledScope) {
461        let mut scopes = self.scopes.lock().unwrap();
462        scopes.remove(&scope.full_path());
463    }
464
465    /// Returns all currently active scopes.
466    ///
467    /// Perfect for debugging and monitoring workflow state.
468    pub fn active_scopes(&self) -> Vec<LabeledScope> {
469        let scopes = self.scopes.lock().unwrap();
470        scopes.values().cloned().collect()
471    }
472
473    /// Finds active scopes with the given label.
474    ///
475    /// Useful for finding all active workflows or runs with a specific identifier.
476    pub fn find_by_label(&self, label: &str) -> Vec<LabeledScope> {
477        let scopes = self.scopes.lock().unwrap();
478        scopes.values()
479            .filter(|scope| scope.label() == label)
480            .cloned()
481            .collect()
482    }
483
484    /// Finds active scopes at the given depth.
485    ///
486    /// Useful for finding all workflows (depth 0), runs (depth 1), or nodes (depth 2).
487    pub fn find_by_depth(&self, depth: usize) -> Vec<LabeledScope> {
488        let scopes = self.scopes.lock().unwrap();
489        scopes.values()
490            .filter(|scope| scope.depth() == depth)
491            .cloned()
492            .collect()
493    }
494
495    /// Returns the count of active scopes.
496    pub fn active_count(&self) -> usize {
497        let scopes = self.scopes.lock().unwrap();
498        scopes.len()
499    }
500
501    /// Clears all registered scopes.
502    ///
503    /// Useful for cleanup during shutdown.
504    pub fn clear(&self) {
505        let mut scopes = self.scopes.lock().unwrap();
506        scopes.clear();
507    }
508}
509
510#[cfg(test)]
511mod tests {
512    use super::*;
513    use crate::ServiceCollection;
514
515    #[test]
516    fn test_labeled_scope_creation() {
517        let services = ServiceCollection::new();
518        let provider = services.build();
519        
520        let scope = provider.create_labeled_scope("test_workflow");
521        assert_eq!(scope.label(), "test_workflow");
522        assert_eq!(scope.depth(), 0);
523        assert!(scope.parent().is_none());
524    }
525
526    #[test]
527    fn test_scope_forking() {
528        let services = ServiceCollection::new();
529        let provider = services.build();
530        
531        let workflow = provider.create_labeled_scope("workflow");
532        let run = workflow.fork("run_001");
533        let node = run.fork("transform");
534        
535        assert_eq!(workflow.label(), "workflow");
536        assert_eq!(run.label(), "run_001");
537        assert_eq!(node.label(), "transform");
538        
539        assert_eq!(workflow.depth(), 0);
540        assert_eq!(run.depth(), 1);
541        assert_eq!(node.depth(), 2);
542    }
543
544    #[test]
545    fn test_full_path() {
546        let services = ServiceCollection::new();
547        let provider = services.build();
548        
549        let scope = provider.create_labeled_scope("workflow")
550            .fork("run_001")
551            .fork("transform_node");
552        
553        assert_eq!(scope.full_path(), "workflow/run_001/transform_node");
554    }
555
556    #[test]
557    fn test_ancestors() {
558        let services = ServiceCollection::new();
559        let provider = services.build();
560        
561        let scope = provider.create_labeled_scope("workflow")
562            .fork("run_001")
563            .fork("transform");
564        
565        let ancestors = scope.ancestors();
566        assert_eq!(ancestors, vec!["workflow", "run_001", "transform"]);
567    }
568
569    #[test]
570    fn test_find_ancestor() {
571        let services = ServiceCollection::new();
572        let provider = services.build();
573        
574        let workflow = provider.create_labeled_scope("workflow");
575        let run = workflow.fork("run_001");
576        let node = run.fork("transform");
577        
578        let found_workflow = node.find_ancestor("workflow").unwrap();
579        assert_eq!(found_workflow.label(), "workflow");
580        
581        let found_run = node.find_ancestor("run_001").unwrap();
582        assert_eq!(found_run.label(), "run_001");
583        
584        assert!(node.find_ancestor("nonexistent").is_none());
585    }
586
587    #[test]
588    fn test_is_ancestor_of() {
589        let services = ServiceCollection::new();
590        let provider = services.build();
591        
592        let workflow = provider.create_labeled_scope("workflow");
593        let run = workflow.fork("run");
594        let node = run.fork("node");
595        
596        assert!(workflow.is_ancestor_of(&node));
597        assert!(run.is_ancestor_of(&node));
598        assert!(node.is_ancestor_of(&node)); // Self is ancestor
599        assert!(!node.is_ancestor_of(&workflow));
600    }
601
602    #[test]
603    fn test_scope_metadata() {
604        let services = ServiceCollection::new();
605        let provider = services.build();
606        
607        let scope = provider.create_labeled_scope("workflow")
608            .fork("run_001");
609        
610        let metadata = scope.metadata();
611        assert_eq!(metadata.label, "run_001");
612        assert_eq!(metadata.depth, 1);
613        assert_eq!(metadata.full_path, "workflow/run_001");
614        assert_eq!(metadata.ancestors, vec!["workflow", "run_001"]);
615        assert_eq!(metadata.parent_label, Some("workflow"));
616    }
617
618    #[test]
619    fn test_scope_registry() {
620        let registry = LabeledScopeRegistry::new();
621        let services = ServiceCollection::new();
622        let provider = services.build();
623        
624        let scope1 = provider.create_labeled_scope("workflow_1");
625        let scope2 = provider.create_labeled_scope("workflow_2");
626        
627        registry.register(&scope1);
628        registry.register(&scope2);
629        
630        assert_eq!(registry.active_count(), 2);
631        
632        let active = registry.active_scopes();
633        assert_eq!(active.len(), 2);
634        
635        registry.unregister(&scope1);
636        assert_eq!(registry.active_count(), 1);
637        
638        registry.clear();
639        assert_eq!(registry.active_count(), 0);
640    }
641
642    #[test]
643    fn test_registry_find_operations() {
644        let registry = LabeledScopeRegistry::new();
645        let services = ServiceCollection::new();
646        let provider = services.build();
647        
648        let workflow1 = provider.create_labeled_scope("workflow");
649        let workflow2 = provider.create_labeled_scope("another_workflow");
650        let run1 = workflow1.fork("run");
651        
652        registry.register(&workflow1);
653        registry.register(&workflow2);
654        registry.register(&run1);
655        
656        // Find by label
657        let workflows = registry.find_by_label("workflow");
658        assert_eq!(workflows.len(), 1);
659        
660        let runs = registry.find_by_label("run");
661        assert_eq!(runs.len(), 1);
662        
663        // Find by depth
664        let depth_0 = registry.find_by_depth(0);
665        assert_eq!(depth_0.len(), 2); // Two workflows
666        
667        let depth_1 = registry.find_by_depth(1);
668        assert_eq!(depth_1.len(), 1); // One run
669    }
670}