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}