oxigdal_workflow/integrations/
mod.rs1pub mod external;
11
12#[cfg(feature = "integrations")]
13pub mod airflow;
14#[cfg(feature = "integrations")]
15pub mod prefect;
16#[cfg(feature = "integrations")]
17pub mod temporal;
18
19use crate::engine::WorkflowDefinition;
20use crate::error::{Result, WorkflowError};
21use serde::{Deserialize, Serialize};
22use std::collections::HashMap;
23
24#[cfg(feature = "integrations")]
25pub use airflow::AirflowIntegration;
26#[cfg(feature = "integrations")]
27pub use prefect::PrefectIntegration;
28#[cfg(feature = "integrations")]
29pub use temporal::TemporalIntegration;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
33pub enum IntegrationType {
34 Airflow,
36 Prefect,
38 Temporal,
40 Webhook,
42 Kafka,
44 RabbitMq,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct IntegrationConfig {
51 pub integration_type: IntegrationType,
53 pub endpoint: String,
55 pub auth: Option<AuthConfig>,
57 pub extra_config: HashMap<String, String>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub enum AuthConfig {
64 ApiKey {
66 key: String,
68 },
69 Basic {
71 username: String,
73 password: String,
75 },
76 OAuth2 {
78 token: String,
80 },
81 None,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct WebhookConfig {
88 pub url: String,
90 pub method: HttpMethod,
92 pub headers: HashMap<String, String>,
94 pub auth: Option<AuthConfig>,
96 pub retry: Option<RetryConfig>,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
102pub enum HttpMethod {
103 Get,
105 Post,
107 Put,
109 Delete,
111 Patch,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct RetryConfig {
118 pub max_retries: usize,
120 pub initial_delay_ms: u64,
122 pub max_delay_ms: u64,
124 pub backoff_multiplier: f64,
126}
127
128impl Default for RetryConfig {
129 fn default() -> Self {
130 Self {
131 max_retries: 3,
132 initial_delay_ms: 1000,
133 max_delay_ms: 30000,
134 backoff_multiplier: 2.0,
135 }
136 }
137}
138
139pub struct IntegrationManager {
141 configs: HashMap<String, IntegrationConfig>,
142}
143
144impl IntegrationManager {
145 pub fn new() -> Self {
147 Self {
148 configs: HashMap::new(),
149 }
150 }
151
152 pub fn register(&mut self, name: String, config: IntegrationConfig) {
154 self.configs.insert(name, config);
155 }
156
157 pub fn get(&self, name: &str) -> Option<&IntegrationConfig> {
159 self.configs.get(name)
160 }
161
162 pub fn remove(&mut self, name: &str) -> Option<IntegrationConfig> {
164 self.configs.remove(name)
165 }
166
167 pub fn list(&self) -> Vec<String> {
169 self.configs.keys().cloned().collect()
170 }
171
172 #[cfg_attr(not(feature = "integrations"), allow(unused_variables))]
174 pub fn export_workflow(
175 &self,
176 workflow: &WorkflowDefinition,
177 integration_type: IntegrationType,
178 ) -> Result<String> {
179 match integration_type {
180 #[cfg(feature = "integrations")]
181 IntegrationType::Airflow => AirflowIntegration::export_workflow(workflow),
182 #[cfg(feature = "integrations")]
183 IntegrationType::Prefect => PrefectIntegration::export_workflow(workflow),
184 #[cfg(feature = "integrations")]
185 IntegrationType::Temporal => TemporalIntegration::export_workflow(workflow),
186 _ => Err(WorkflowError::integration(
187 integration_type.as_str(),
188 "Export not implemented for this integration type",
189 )),
190 }
191 }
192
193 #[cfg(feature = "integrations")]
195 pub async fn trigger_webhook(
196 &self,
197 config: &WebhookConfig,
198 payload: &serde_json::Value,
199 ) -> Result<String> {
200 use reqwest::Client;
201
202 let client = Client::new();
203 let mut request = match config.method {
204 HttpMethod::Get => client.get(&config.url),
205 HttpMethod::Post => client.post(&config.url),
206 HttpMethod::Put => client.put(&config.url),
207 HttpMethod::Delete => client.delete(&config.url),
208 HttpMethod::Patch => client.patch(&config.url),
209 };
210
211 for (key, value) in &config.headers {
213 request = request.header(key, value);
214 }
215
216 if let Some(auth) = &config.auth {
218 request = match auth {
219 AuthConfig::ApiKey { key } => request.header("X-API-Key", key),
220 AuthConfig::Basic { username, password } => {
221 request.basic_auth(username, Some(password))
222 }
223 AuthConfig::OAuth2 { token } => request.bearer_auth(token),
224 AuthConfig::None => request,
225 };
226 }
227
228 let response =
230 request.json(payload).send().await.map_err(|e| {
231 WorkflowError::integration("webhook", format!("Request failed: {}", e))
232 })?;
233
234 let status = response.status();
235 let body = response.text().await.map_err(|e| {
236 WorkflowError::integration("webhook", format!("Failed to read response: {}", e))
237 })?;
238
239 if !status.is_success() {
240 return Err(WorkflowError::integration(
241 "webhook",
242 format!("Request failed with status {}: {}", status, body),
243 ));
244 }
245
246 Ok(body)
247 }
248}
249
250impl Default for IntegrationManager {
251 fn default() -> Self {
252 Self::new()
253 }
254}
255
256impl IntegrationType {
257 pub fn as_str(&self) -> &'static str {
259 match self {
260 Self::Airflow => "airflow",
261 Self::Prefect => "prefect",
262 Self::Temporal => "temporal",
263 Self::Webhook => "webhook",
264 Self::Kafka => "kafka",
265 Self::RabbitMq => "rabbitmq",
266 }
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
275 fn test_integration_manager() {
276 let mut manager = IntegrationManager::new();
277
278 let config = IntegrationConfig {
279 integration_type: IntegrationType::Webhook,
280 endpoint: "https://example.com/webhook".to_string(),
281 auth: Some(AuthConfig::ApiKey {
282 key: "test-key".to_string(),
283 }),
284 extra_config: HashMap::new(),
285 };
286
287 manager.register("test-integration".to_string(), config);
288
289 assert!(manager.get("test-integration").is_some());
290 assert_eq!(manager.list().len(), 1);
291 }
292
293 #[test]
294 fn test_integration_type_str() {
295 assert_eq!(IntegrationType::Airflow.as_str(), "airflow");
296 assert_eq!(IntegrationType::Webhook.as_str(), "webhook");
297 }
298
299 #[test]
300 fn test_retry_config_default() {
301 let config = RetryConfig::default();
302 assert_eq!(config.max_retries, 3);
303 assert_eq!(config.initial_delay_ms, 1000);
304 }
305}