rust_supervisor/tree/builder.rs
1//! Supervisor tree builder.
2//!
3//! This module converts declarations into indexed tree nodes while preserving
4//! declaration order.
5
6use crate::error::types::SupervisorError;
7use crate::id::types::{ChildId, SupervisorPath};
8use crate::spec::child::{ChildSpec, TaskKind};
9use crate::spec::supervisor::SupervisorSpec;
10use std::collections::HashSet;
11
12/// Node in a supervisor tree.
13#[derive(Debug, Clone)]
14pub struct SupervisorTreeNode {
15 /// Path of the node in the tree.
16 pub path: SupervisorPath,
17 /// Child declaration attached to this node.
18 pub child: ChildSpec,
19 /// Zero-based declaration order under the parent.
20 pub declaration_index: usize,
21}
22
23/// Built supervisor tree with stable declaration order.
24#[derive(Debug, Clone)]
25pub struct SupervisorTree {
26 /// Root supervisor path.
27 pub root_path: SupervisorPath,
28 /// Nodes in declaration order.
29 pub nodes: Vec<SupervisorTreeNode>,
30}
31
32impl SupervisorTree {
33 /// Builds a tree from a supervisor specification.
34 ///
35 /// # Arguments
36 ///
37 /// - `spec`: Supervisor declaration to index.
38 ///
39 /// # Returns
40 ///
41 /// Returns a [`SupervisorTree`] when child identifiers and paths are valid.
42 ///
43 /// # Examples
44 ///
45 /// ```
46 /// let spec = rust_supervisor::spec::supervisor::SupervisorSpec::root(Vec::new());
47 /// let tree = rust_supervisor::tree::builder::SupervisorTree::build(&spec).unwrap();
48 /// assert!(tree.nodes.is_empty());
49 /// ```
50 pub fn build(spec: &SupervisorSpec) -> Result<Self, SupervisorError> {
51 spec.validate()?;
52 let mut seen = HashSet::new();
53 let mut nodes = Vec::with_capacity(spec.children.len());
54 for (index, child) in spec.children.iter().enumerate() {
55 validate_child_path(&spec.path, &child.id, &mut seen)?;
56 nodes.push(SupervisorTreeNode {
57 path: spec.path.join(&child.id.value),
58 child: child.clone(),
59 declaration_index: index,
60 });
61 }
62 Ok(Self {
63 root_path: spec.path.clone(),
64 nodes,
65 })
66 }
67
68 /// Returns the path for a child identifier.
69 ///
70 /// # Arguments
71 ///
72 /// - `child_id`: Child identifier to locate.
73 ///
74 /// # Returns
75 ///
76 /// Returns the child path when the child exists.
77 pub fn child_path(&self, child_id: &ChildId) -> Option<SupervisorPath> {
78 self.nodes
79 .iter()
80 .find(|node| node.child.id == *child_id)
81 .map(|node| node.path.clone())
82 }
83
84 /// Returns supervisor nodes declared as nested supervisors.
85 ///
86 /// # Arguments
87 ///
88 /// This function has no arguments.
89 ///
90 /// # Returns
91 ///
92 /// Returns nested supervisor nodes in declaration order.
93 pub fn nested_supervisors(&self) -> Vec<&SupervisorTreeNode> {
94 self.nodes
95 .iter()
96 .filter(|node| node.child.kind == TaskKind::Supervisor)
97 .collect()
98 }
99}
100
101/// Validates path uniqueness for a child under a parent path.
102///
103/// # Arguments
104///
105/// - `parent`: Parent supervisor path.
106/// - `child_id`: Child identifier being appended to the path.
107/// - `seen`: Set of paths already declared under the parent.
108///
109/// # Returns
110///
111/// Returns `Ok(())` when the path is unique.
112fn validate_child_path(
113 parent: &SupervisorPath,
114 child_id: &ChildId,
115 seen: &mut HashSet<String>,
116) -> Result<(), SupervisorError> {
117 let path = parent.join(&child_id.value).to_string();
118 if seen.insert(path) {
119 Ok(())
120 } else {
121 Err(SupervisorError::fatal_config(format!(
122 "duplicate child path for {child_id}"
123 )))
124 }
125}