Skip to main content

forge_runtime/webhook/
registry.rs

1//! Registry for webhook handlers.
2
3use std::collections::HashMap;
4use std::future::Future;
5use std::pin::Pin;
6use std::sync::Arc;
7
8use forge_core::Result;
9use forge_core::webhook::{ForgeWebhook, WebhookContext, WebhookInfo, WebhookResult};
10use serde_json::Value;
11
12/// Type alias for boxed webhook handler functions.
13pub type BoxedWebhookHandler = Arc<
14    dyn Fn(
15            &WebhookContext,
16            Value,
17        ) -> Pin<Box<dyn Future<Output = Result<WebhookResult>> + Send + '_>>
18        + Send
19        + Sync,
20>;
21
22/// Entry in the webhook registry.
23pub struct WebhookEntry {
24    /// Webhook metadata.
25    pub info: WebhookInfo,
26    /// Webhook handler function.
27    pub handler: BoxedWebhookHandler,
28}
29
30/// Registry for storing and retrieving webhook handlers.
31#[derive(Clone, Default)]
32pub struct WebhookRegistry {
33    webhooks: HashMap<String, Arc<WebhookEntry>>,
34    /// Index by path for routing.
35    by_path: HashMap<String, String>,
36}
37
38impl WebhookRegistry {
39    /// Create a new empty registry.
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    /// Register a webhook handler.
45    pub fn register<W: ForgeWebhook>(&mut self) {
46        let info = W::info();
47        let name = info.name.to_string();
48        let path = info.path.to_string();
49
50        let handler: BoxedWebhookHandler = Arc::new(move |ctx, payload| W::execute(ctx, payload));
51
52        self.by_path.insert(path, name.clone());
53        self.webhooks
54            .insert(name, Arc::new(WebhookEntry { info, handler }));
55    }
56
57    /// Get a webhook entry by name.
58    pub fn get(&self, name: &str) -> Option<Arc<WebhookEntry>> {
59        self.webhooks.get(name).cloned()
60    }
61
62    /// Get a webhook entry by path.
63    pub fn get_by_path(&self, path: &str) -> Option<Arc<WebhookEntry>> {
64        self.by_path.get(path).and_then(|name| self.get(name))
65    }
66
67    /// Get webhook info by name.
68    pub fn info(&self, name: &str) -> Option<&WebhookInfo> {
69        self.webhooks.get(name).map(|e| &e.info)
70    }
71
72    /// Check if a webhook exists.
73    pub fn exists(&self, name: &str) -> bool {
74        self.webhooks.contains_key(name)
75    }
76
77    /// Check if a path is registered.
78    pub fn path_exists(&self, path: &str) -> bool {
79        self.by_path.contains_key(path)
80    }
81
82    /// Get all webhook names.
83    pub fn webhook_names(&self) -> impl Iterator<Item = &str> {
84        self.webhooks.keys().map(|s| s.as_str())
85    }
86
87    /// Get all registered paths.
88    pub fn paths(&self) -> impl Iterator<Item = &str> {
89        self.by_path.keys().map(|s| s.as_str())
90    }
91
92    /// Get all webhooks.
93    pub fn webhooks(&self) -> impl Iterator<Item = (&str, &Arc<WebhookEntry>)> {
94        self.webhooks.iter().map(|(k, v)| (k.as_str(), v))
95    }
96
97    /// Get the number of registered webhooks.
98    pub fn len(&self) -> usize {
99        self.webhooks.len()
100    }
101
102    /// Check if registry is empty.
103    pub fn is_empty(&self) -> bool {
104        self.webhooks.is_empty()
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_registry_new() {
114        let registry = WebhookRegistry::new();
115        assert!(registry.is_empty());
116    }
117}