Skip to main content

rust_supervisor/task/
factory_registry.rs

1//! Task factory registry for declarative worker configuration.
2//!
3//! This module owns the mapping from task factory keys to executable
4//! [`TaskFactory`] values. Configuration
5//! code uses the same registry to validate `factory_key` declarations and to
6//! generate editor completion metadata.
7
8use crate::error::types::SupervisorError;
9use crate::spec::child::TaskKind;
10use crate::task::factory::TaskFactory;
11use std::collections::BTreeMap;
12use std::fmt::{Debug, Formatter};
13use std::sync::Arc;
14
15/// Metadata and executable factory for one registered task kind.
16#[derive(Clone)]
17pub struct TaskFactoryDescriptor {
18    /// Task factory key used in YAML `factory_key` fields.
19    pub key: String,
20    /// Short display title used by schema completion.
21    pub title: String,
22    /// Human-readable description used by schema completion.
23    pub description: String,
24    /// Task kinds that may use this factory.
25    pub allowed_kinds: Vec<TaskKind>,
26    /// Factory used to build each child attempt.
27    pub factory: Arc<dyn TaskFactory>,
28}
29
30impl TaskFactoryDescriptor {
31    /// Creates a task factory descriptor.
32    ///
33    /// # Arguments
34    ///
35    /// - `key`: Task factory key used in YAML `factory_key` fields.
36    /// - `title`: Short display title used by schema completion.
37    /// - `description`: Human-readable description used by schema completion.
38    /// - `allowed_kinds`: Task kinds that may use this factory.
39    /// - `factory`: Factory used to build each child attempt.
40    ///
41    /// # Returns
42    ///
43    /// Returns a [`TaskFactoryDescriptor`] value.
44    ///
45    /// # Examples
46    ///
47    /// ```
48    /// use rust_supervisor::spec::child::TaskKind;
49    /// use rust_supervisor::task::factory::{TaskResult, service_fn};
50    /// use rust_supervisor::task::factory_registry::TaskFactoryDescriptor;
51    /// use std::sync::Arc;
52    ///
53    /// let descriptor = TaskFactoryDescriptor::new(
54    ///     "worker",
55    ///     "Worker",
56    ///     "Runs one worker task.",
57    ///     [TaskKind::AsyncWorker],
58    ///     Arc::new(service_fn(|_ctx| async { TaskResult::Succeeded })),
59    /// );
60    /// assert_eq!(descriptor.key, "worker");
61    /// ```
62    pub fn new(
63        key: impl Into<String>,
64        title: impl Into<String>,
65        description: impl Into<String>,
66        allowed_kinds: impl IntoIterator<Item = TaskKind>,
67        factory: Arc<dyn TaskFactory>,
68    ) -> Self {
69        Self {
70            key: key.into(),
71            title: title.into(),
72            description: description.into(),
73            allowed_kinds: allowed_kinds.into_iter().collect(),
74            factory,
75        }
76    }
77}
78
79impl Debug for TaskFactoryDescriptor {
80    /// Formats descriptor metadata without printing the executable factory.
81    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
82        formatter
83            .debug_struct("TaskFactoryDescriptor")
84            .field("key", &self.key)
85            .field("title", &self.title)
86            .field("description", &self.description)
87            .field("allowed_kinds", &self.allowed_kinds)
88            .finish()
89    }
90}
91
92/// Registry that resolves declarative task factory keys.
93#[derive(Clone, Default)]
94pub struct TaskFactoryRegistry {
95    /// Factory descriptors indexed by key.
96    entries: BTreeMap<String, TaskFactoryDescriptor>,
97}
98
99impl Debug for TaskFactoryRegistry {
100    /// Formats registry keys without printing executable factories.
101    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
102        formatter
103            .debug_struct("TaskFactoryRegistry")
104            .field("keys", &self.keys())
105            .finish()
106    }
107}
108
109impl TaskFactoryRegistry {
110    /// Creates an empty task factory registry.
111    ///
112    /// # Arguments
113    ///
114    /// This function has no arguments.
115    ///
116    /// # Returns
117    ///
118    /// Returns an empty [`TaskFactoryRegistry`].
119    ///
120    /// # Examples
121    ///
122    /// ```
123    /// let registry = rust_supervisor::task::factory_registry::TaskFactoryRegistry::new();
124    /// assert!(registry.is_empty());
125    /// ```
126    pub fn new() -> Self {
127        Self::default()
128    }
129
130    /// Registers one task factory descriptor.
131    ///
132    /// # Arguments
133    ///
134    /// - `descriptor`: Descriptor that owns the factory and completion metadata.
135    ///
136    /// # Returns
137    ///
138    /// Returns `Ok(())` when the key is valid and unused.
139    ///
140    /// # Errors
141    ///
142    /// Returns [`SupervisorError`] when the key is invalid, duplicate, or has no
143    /// allowed task kind.
144    pub fn register(&mut self, descriptor: TaskFactoryDescriptor) -> Result<(), SupervisorError> {
145        if !is_valid_factory_key(&descriptor.key) {
146            return Err(SupervisorError::fatal_config(format!(
147                "task factory key '{}' must match ^[a-zA-Z_][a-zA-Z0-9_-]*$",
148                descriptor.key
149            )));
150        }
151        if descriptor.allowed_kinds.is_empty() {
152            return Err(SupervisorError::fatal_config(format!(
153                "task factory key '{}' must allow at least one task kind",
154                descriptor.key
155            )));
156        }
157        if self.entries.contains_key(&descriptor.key) {
158            return Err(SupervisorError::fatal_config(format!(
159                "duplicate task factory key '{}'",
160                descriptor.key
161            )));
162        }
163
164        self.entries.insert(descriptor.key.clone(), descriptor);
165        Ok(())
166    }
167
168    /// Resolves a registered factory for a task kind.
169    ///
170    /// # Arguments
171    ///
172    /// - `key`: Factory key loaded from configuration.
173    /// - `kind`: Task kind declared by the child.
174    ///
175    /// # Returns
176    ///
177    /// Returns the registered factory when the key exists and supports `kind`.
178    ///
179    /// # Errors
180    ///
181    /// Returns [`SupervisorError`] when the key is unknown or cannot be used for
182    /// the requested task kind.
183    pub fn resolve(
184        &self,
185        key: &str,
186        kind: TaskKind,
187    ) -> Result<Arc<dyn TaskFactory>, SupervisorError> {
188        let descriptor = self.entries.get(key).ok_or_else(|| {
189            SupervisorError::fatal_config(format!("unknown task factory key '{key}'"))
190        })?;
191        if !descriptor.allowed_kinds.contains(&kind) {
192            return Err(SupervisorError::fatal_config(format!(
193                "task factory key '{key}' does not support task kind {kind:?}"
194            )));
195        }
196        Ok(descriptor.factory.clone())
197    }
198
199    /// Returns descriptors in sorted key order.
200    ///
201    /// # Arguments
202    ///
203    /// This function has no arguments.
204    ///
205    /// # Returns
206    ///
207    /// Returns registered descriptors sorted by key.
208    pub fn descriptors(&self) -> Vec<&TaskFactoryDescriptor> {
209        self.entries.values().collect()
210    }
211
212    /// Returns registered keys in stable order.
213    ///
214    /// # Arguments
215    ///
216    /// This function has no arguments.
217    ///
218    /// # Returns
219    ///
220    /// Returns registered factory keys sorted by key.
221    pub fn keys(&self) -> Vec<String> {
222        self.entries.keys().cloned().collect()
223    }
224
225    /// Returns whether the registry has no entries.
226    ///
227    /// # Arguments
228    ///
229    /// This function has no arguments.
230    ///
231    /// # Returns
232    ///
233    /// Returns `true` when no factory is registered.
234    pub fn is_empty(&self) -> bool {
235        self.entries.is_empty()
236    }
237}
238
239/// Returns whether a factory key is valid for configuration use.
240///
241/// # Arguments
242///
243/// - `key`: Candidate key text.
244///
245/// # Returns
246///
247/// Returns `true` when the key matches the supported identifier surface.
248pub fn is_valid_factory_key(key: &str) -> bool {
249    if key.is_empty() {
250        return false;
251    }
252    let first = key.chars().next().unwrap();
253    if !first.is_ascii_alphabetic() && first != '_' {
254        return false;
255    }
256    key.chars()
257        .all(|character| character.is_ascii_alphanumeric() || character == '_' || character == '-')
258}