forge_runtime/workflow/
registry.rs

1use std::collections::HashMap;
2use std::future::Future;
3use std::pin::Pin;
4use std::sync::Arc;
5
6use forge_core::workflow::{ForgeWorkflow, WorkflowContext, WorkflowInfo};
7
8/// Type alias for boxed workflow handler function.
9pub type BoxedWorkflowHandler = Arc<
10    dyn Fn(
11            &WorkflowContext,
12            serde_json::Value,
13        )
14            -> Pin<Box<dyn Future<Output = forge_core::Result<serde_json::Value>> + Send + '_>>
15        + Send
16        + Sync,
17>;
18
19/// A registered workflow entry.
20pub struct WorkflowEntry {
21    /// Workflow metadata.
22    pub info: WorkflowInfo,
23    /// Execution handler (takes serialized input, returns serialized output).
24    pub handler: BoxedWorkflowHandler,
25}
26
27impl WorkflowEntry {
28    /// Create a new workflow entry from a ForgeWorkflow implementor.
29    pub fn new<W: ForgeWorkflow>() -> Self
30    where
31        W::Input: serde::de::DeserializeOwned,
32        W::Output: serde::Serialize,
33    {
34        Self {
35            info: W::info(),
36            handler: Arc::new(|ctx, input| {
37                Box::pin(async move {
38                    let typed_input: W::Input = serde_json::from_value(input)
39                        .map_err(|e| forge_core::ForgeError::Validation(e.to_string()))?;
40                    let result = W::execute(ctx, typed_input).await?;
41                    serde_json::to_value(result).map_err(forge_core::ForgeError::from)
42                })
43            }),
44        }
45    }
46}
47
48/// Registry of all workflows.
49#[derive(Default)]
50pub struct WorkflowRegistry {
51    workflows: HashMap<String, WorkflowEntry>,
52}
53
54impl WorkflowRegistry {
55    /// Create a new empty registry.
56    pub fn new() -> Self {
57        Self {
58            workflows: HashMap::new(),
59        }
60    }
61
62    /// Register a workflow handler.
63    pub fn register<W: ForgeWorkflow>(&mut self)
64    where
65        W::Input: serde::de::DeserializeOwned,
66        W::Output: serde::Serialize,
67    {
68        let entry = WorkflowEntry::new::<W>();
69        self.workflows.insert(entry.info.name.to_string(), entry);
70    }
71
72    /// Get a workflow entry by name.
73    pub fn get(&self, name: &str) -> Option<&WorkflowEntry> {
74        self.workflows.get(name)
75    }
76
77    /// Get a workflow entry by name and version.
78    pub fn get_version(&self, name: &str, version: u32) -> Option<&WorkflowEntry> {
79        self.workflows
80            .get(name)
81            .filter(|e| e.info.version == version)
82    }
83
84    /// List all registered workflows.
85    pub fn list(&self) -> Vec<&WorkflowEntry> {
86        self.workflows.values().collect()
87    }
88
89    /// Get the number of registered workflows.
90    pub fn len(&self) -> usize {
91        self.workflows.len()
92    }
93
94    /// Check if the registry is empty.
95    pub fn is_empty(&self) -> bool {
96        self.workflows.is_empty()
97    }
98
99    /// Get all workflow names.
100    pub fn names(&self) -> Vec<&str> {
101        self.workflows.keys().map(|s| s.as_str()).collect()
102    }
103}
104
105impl Clone for WorkflowRegistry {
106    fn clone(&self) -> Self {
107        Self {
108            workflows: self
109                .workflows
110                .iter()
111                .map(|(k, v)| {
112                    (
113                        k.clone(),
114                        WorkflowEntry {
115                            info: v.info.clone(),
116                            handler: v.handler.clone(),
117                        },
118                    )
119                })
120                .collect(),
121        }
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_empty_registry() {
131        let registry = WorkflowRegistry::new();
132        assert!(registry.is_empty());
133        assert_eq!(registry.len(), 0);
134    }
135}