1use crate::engine::state::{TaskState, WorkflowState};
13use crate::error::{Result, WorkflowError};
14use async_trait::async_trait;
15use chrono::{DateTime, Utc};
16use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18use std::sync::Arc;
19use std::time::Duration;
20use tokio::sync::RwLock;
21#[cfg(feature = "integrations")]
22use tracing::debug;
23use tracing::{error, info, warn};
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct HttpClientConfig {
32 pub base_url: String,
34 pub timeout_secs: u64,
36 pub max_retries: u32,
38 pub retry_delay_ms: u64,
40 pub default_headers: HashMap<String, String>,
42 pub auth: Option<HttpAuth>,
44 pub enable_logging: bool,
46}
47
48impl Default for HttpClientConfig {
49 fn default() -> Self {
50 Self {
51 base_url: String::new(),
52 timeout_secs: 30,
53 max_retries: 3,
54 retry_delay_ms: 1000,
55 default_headers: HashMap::new(),
56 auth: None,
57 enable_logging: false,
58 }
59 }
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub enum HttpAuth {
65 Bearer {
67 token: String,
69 },
70 Basic {
72 username: String,
74 password: String,
76 },
77 ApiKey {
79 header_name: String,
81 key: String,
83 },
84 Custom {
86 headers: HashMap<String, String>,
88 },
89}
90
91#[derive(Debug, Clone)]
93pub struct HttpRequest {
94 pub method: HttpMethod,
96 pub path: String,
98 pub query_params: HashMap<String, String>,
100 pub headers: HashMap<String, String>,
102 pub body: Option<serde_json::Value>,
104 pub timeout: Option<Duration>,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
110pub enum HttpMethod {
111 Get,
113 Post,
115 Put,
117 Patch,
119 Delete,
121 Head,
123 Options,
125}
126
127impl HttpMethod {
128 pub fn as_str(&self) -> &'static str {
130 match self {
131 Self::Get => "GET",
132 Self::Post => "POST",
133 Self::Put => "PUT",
134 Self::Patch => "PATCH",
135 Self::Delete => "DELETE",
136 Self::Head => "HEAD",
137 Self::Options => "OPTIONS",
138 }
139 }
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct HttpResponse {
145 pub status_code: u16,
147 pub headers: HashMap<String, String>,
149 pub body: Option<serde_json::Value>,
151 pub response_time_ms: u64,
153 pub is_success: bool,
155}
156
157impl HttpResponse {
158 pub fn is_success(&self) -> bool {
160 (200..300).contains(&self.status_code)
161 }
162
163 pub fn is_client_error(&self) -> bool {
165 (400..500).contains(&self.status_code)
166 }
167
168 pub fn is_server_error(&self) -> bool {
170 (500..600).contains(&self.status_code)
171 }
172}
173
174#[cfg(feature = "integrations")]
180fn url_with_query_params(base_url: &str, params: &HashMap<String, String>) -> String {
181 match reqwest::Url::parse(base_url) {
182 Ok(mut url) => {
183 {
184 let mut pairs = url.query_pairs_mut();
185 for (k, v) in params {
186 pairs.append_pair(k, v);
187 }
188 }
189 url.to_string()
190 }
191 Err(_) => base_url.to_owned(),
192 }
193}
194
195#[cfg(feature = "integrations")]
197pub struct RestApiClient {
198 config: HttpClientConfig,
199 client: reqwest::Client,
200}
201
202#[cfg(feature = "integrations")]
203impl RestApiClient {
204 pub fn new(config: HttpClientConfig) -> Result<Self> {
206 let client = reqwest::Client::builder()
207 .timeout(Duration::from_secs(config.timeout_secs))
208 .build()
209 .map_err(|e| {
210 WorkflowError::integration("rest", format!("Failed to create client: {}", e))
211 })?;
212
213 Ok(Self { config, client })
214 }
215
216 pub async fn execute(&self, request: HttpRequest) -> Result<HttpResponse> {
218 let url = if request.path.starts_with("http") {
219 request.path.clone()
220 } else {
221 format!("{}{}", self.config.base_url, request.path)
222 };
223
224 let mut last_error = None;
225
226 for attempt in 0..=self.config.max_retries {
227 if attempt > 0 {
228 debug!("Retrying request (attempt {})", attempt + 1);
229 tokio::time::sleep(Duration::from_millis(
230 self.config.retry_delay_ms * (1 << attempt.min(5)),
231 ))
232 .await;
233 }
234
235 match self.do_request(&url, &request).await {
236 Ok(response) => {
237 if self.config.enable_logging {
238 info!(
239 "HTTP {} {} -> {} ({}ms)",
240 request.method.as_str(),
241 url,
242 response.status_code,
243 response.response_time_ms
244 );
245 }
246 return Ok(response);
247 }
248 Err(e) => {
249 last_error = Some(e);
250 if attempt < self.config.max_retries {
251 warn!("Request failed, will retry: {:?}", last_error);
252 }
253 }
254 }
255 }
256
257 Err(last_error.unwrap_or_else(|| {
258 WorkflowError::integration("rest", "Request failed after all retries")
259 }))
260 }
261
262 async fn do_request(&self, url: &str, request: &HttpRequest) -> Result<HttpResponse> {
263 let start_time = std::time::Instant::now();
264
265 let effective_url;
269 let request_url = if request.query_params.is_empty() {
270 url
271 } else {
272 effective_url = url_with_query_params(url, &request.query_params);
273 effective_url.as_str()
274 };
275
276 let mut req_builder = match request.method {
277 HttpMethod::Get => self.client.get(request_url),
278 HttpMethod::Post => self.client.post(request_url),
279 HttpMethod::Put => self.client.put(request_url),
280 HttpMethod::Patch => self.client.patch(request_url),
281 HttpMethod::Delete => self.client.delete(request_url),
282 HttpMethod::Head => self.client.head(request_url),
283 HttpMethod::Options => self.client.request(reqwest::Method::OPTIONS, request_url),
284 };
285
286 for (key, value) in &self.config.default_headers {
288 req_builder = req_builder.header(key, value);
289 }
290
291 for (key, value) in &request.headers {
293 req_builder = req_builder.header(key, value);
294 }
295
296 if let Some(auth) = &self.config.auth {
298 req_builder = match auth {
299 HttpAuth::Bearer { token } => req_builder.bearer_auth(token),
300 HttpAuth::Basic { username, password } => {
301 req_builder.basic_auth(username, Some(password))
302 }
303 HttpAuth::ApiKey { header_name, key } => req_builder.header(header_name, key),
304 HttpAuth::Custom { headers } => {
305 for (k, v) in headers {
306 req_builder = req_builder.header(k, v);
307 }
308 req_builder
309 }
310 };
311 }
312
313 if let Some(body) = &request.body {
315 req_builder = req_builder.json(body);
316 }
317
318 if let Some(timeout) = request.timeout {
320 req_builder = req_builder.timeout(timeout);
321 }
322
323 let response = req_builder
325 .send()
326 .await
327 .map_err(|e| WorkflowError::integration("rest", format!("Request failed: {}", e)))?;
328
329 let status_code = response.status().as_u16();
330 let mut headers = HashMap::new();
331 for (key, value) in response.headers() {
332 if let Ok(v) = value.to_str() {
333 headers.insert(key.to_string(), v.to_string());
334 }
335 }
336
337 let body = response.json::<serde_json::Value>().await.ok();
338
339 let response_time_ms = start_time.elapsed().as_millis() as u64;
340
341 Ok(HttpResponse {
342 is_success: (200..300).contains(&status_code),
343 status_code,
344 headers,
345 body,
346 response_time_ms,
347 })
348 }
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct QueueMessage {
358 pub id: String,
360 pub topic: String,
362 pub payload: serde_json::Value,
364 pub headers: HashMap<String, String>,
366 pub timestamp: DateTime<Utc>,
368 pub priority: u8,
370 pub correlation_id: Option<String>,
372 pub reply_to: Option<String>,
374}
375
376impl QueueMessage {
377 pub fn new(topic: impl Into<String>, payload: serde_json::Value) -> Self {
379 Self {
380 id: uuid::Uuid::new_v4().to_string(),
381 topic: topic.into(),
382 payload,
383 headers: HashMap::new(),
384 timestamp: Utc::now(),
385 priority: 5,
386 correlation_id: None,
387 reply_to: None,
388 }
389 }
390
391 pub fn with_correlation_id(mut self, correlation_id: impl Into<String>) -> Self {
393 self.correlation_id = Some(correlation_id.into());
394 self
395 }
396
397 pub fn with_reply_to(mut self, reply_to: impl Into<String>) -> Self {
399 self.reply_to = Some(reply_to.into());
400 self
401 }
402
403 pub fn with_priority(mut self, priority: u8) -> Self {
405 self.priority = priority.min(9);
406 self
407 }
408
409 pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
411 self.headers.insert(key.into(), value.into());
412 self
413 }
414}
415
416#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418pub enum AckStatus {
419 Ack,
421 Nack,
423 Reject,
425}
426
427#[async_trait]
429pub trait MessageQueueProducer: Send + Sync {
430 async fn send(&self, message: QueueMessage) -> Result<()>;
432
433 async fn send_batch(&self, messages: Vec<QueueMessage>) -> Result<Vec<Result<()>>> {
435 let mut results = Vec::with_capacity(messages.len());
436 for msg in messages {
437 results.push(self.send(msg).await);
438 }
439 Ok(results)
440 }
441
442 async fn flush(&self) -> Result<()>;
444}
445
446#[async_trait]
448pub trait MessageQueueConsumer: Send + Sync {
449 async fn subscribe(&self, topic: &str) -> Result<()>;
451
452 async fn unsubscribe(&self, topic: &str) -> Result<()>;
454
455 async fn receive(&self, timeout: Duration) -> Result<Option<QueueMessage>>;
457
458 async fn acknowledge(&self, message_id: &str, status: AckStatus) -> Result<()>;
460
461 async fn commit(&self) -> Result<()>;
463}
464
465#[derive(Debug, Clone, Serialize, Deserialize)]
467pub struct MessageQueueConfig {
468 pub queue_type: MessageQueueType,
470 pub endpoints: Vec<String>,
472 pub auth: Option<QueueAuth>,
474 pub consumer_group: Option<String>,
476 pub retention_secs: u64,
478 pub max_message_size: usize,
480 pub compression: bool,
482}
483
484#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
486pub enum MessageQueueType {
487 Kafka,
489 RabbitMq,
491 Sqs,
493 PubSub,
495 ServiceBus,
497 Redis,
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize)]
503pub enum QueueAuth {
504 Credentials {
506 username: String,
508 password: String,
510 },
511 ApiKey {
513 key: String,
515 },
516 OAuth2 {
518 client_id: String,
520 client_secret: String,
522 token_url: String,
524 },
525 Tls {
527 cert_path: String,
529 key_path: String,
531 ca_path: Option<String>,
533 },
534}
535
536#[derive(Debug, Clone, Serialize, Deserialize)]
542pub struct QueryResult {
543 pub rows_affected: u64,
545 pub columns: Vec<String>,
547 pub rows: Vec<Vec<serde_json::Value>>,
549 pub execution_time_ms: u64,
551}
552
553impl QueryResult {
554 pub fn empty() -> Self {
556 Self {
557 rows_affected: 0,
558 columns: Vec::new(),
559 rows: Vec::new(),
560 execution_time_ms: 0,
561 }
562 }
563
564 pub fn row_count(&self) -> usize {
566 self.rows.len()
567 }
568
569 pub fn is_empty(&self) -> bool {
571 self.rows.is_empty()
572 }
573}
574
575#[async_trait]
577pub trait DatabaseConnection: Send + Sync {
578 async fn query(&self, sql: &str, params: &[serde_json::Value]) -> Result<QueryResult>;
580
581 async fn execute(&self, sql: &str, params: &[serde_json::Value]) -> Result<u64>;
583
584 async fn begin_transaction(&self) -> Result<Box<dyn DatabaseTransaction>>;
586
587 async fn ping(&self) -> Result<()>;
589
590 async fn close(&self) -> Result<()>;
592}
593
594#[async_trait]
596pub trait DatabaseTransaction: Send + Sync {
597 async fn query(&self, sql: &str, params: &[serde_json::Value]) -> Result<QueryResult>;
599
600 async fn execute(&self, sql: &str, params: &[serde_json::Value]) -> Result<u64>;
602
603 async fn commit(self: Box<Self>) -> Result<()>;
605
606 async fn rollback(self: Box<Self>) -> Result<()>;
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize)]
612pub struct DatabaseConfig {
613 pub db_type: DatabaseType,
615 pub connection_string: String,
617 pub database: Option<String>,
619 pub pool_min: u32,
621 pub pool_max: u32,
623 pub connect_timeout_secs: u64,
625 pub query_timeout_secs: u64,
627 pub ssl: bool,
629}
630
631impl Default for DatabaseConfig {
632 fn default() -> Self {
633 Self {
634 db_type: DatabaseType::PostgreSql,
635 connection_string: String::new(),
636 database: None,
637 pool_min: 1,
638 pool_max: 10,
639 connect_timeout_secs: 30,
640 query_timeout_secs: 60,
641 ssl: false,
642 }
643 }
644}
645
646#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
648pub enum DatabaseType {
649 PostgreSql,
651 MySql,
653 Sqlite,
655 MsSql,
657 MongoDb,
659 ClickHouse,
661}
662
663#[derive(Debug, Clone, Serialize, Deserialize)]
669pub struct ObjectMetadata {
670 pub key: String,
672 pub size: u64,
674 pub content_type: Option<String>,
676 pub last_modified: Option<DateTime<Utc>>,
678 pub etag: Option<String>,
680 pub metadata: HashMap<String, String>,
682}
683
684#[derive(Debug, Clone, Default, Serialize, Deserialize)]
686pub struct UploadOptions {
687 pub content_type: Option<String>,
689 pub content_encoding: Option<String>,
691 pub cache_control: Option<String>,
693 pub metadata: HashMap<String, String>,
695 pub encryption: Option<StorageEncryption>,
697 pub storage_class: Option<String>,
699}
700
701#[derive(Debug, Clone, Serialize, Deserialize)]
703pub enum StorageEncryption {
704 ServerManaged,
706 CustomerManaged {
708 key_id: String,
710 },
711}
712
713#[async_trait]
715pub trait CloudStorage: Send + Sync {
716 async fn upload(
718 &self,
719 key: &str,
720 data: &[u8],
721 options: UploadOptions,
722 ) -> Result<ObjectMetadata>;
723
724 async fn download(&self, key: &str) -> Result<Vec<u8>>;
726
727 async fn head(&self, key: &str) -> Result<ObjectMetadata>;
729
730 async fn delete(&self, key: &str) -> Result<()>;
732
733 async fn list(&self, prefix: &str, max_keys: Option<usize>) -> Result<Vec<ObjectMetadata>>;
735
736 async fn copy(&self, source_key: &str, dest_key: &str) -> Result<ObjectMetadata>;
738
739 async fn presigned_download_url(&self, key: &str, expires_in: Duration) -> Result<String>;
741
742 async fn presigned_upload_url(&self, key: &str, expires_in: Duration) -> Result<String>;
744}
745
746#[derive(Debug, Clone, Serialize, Deserialize)]
748pub struct CloudStorageConfig {
749 pub provider: StorageProvider,
751 pub bucket: String,
753 pub region: Option<String>,
755 pub endpoint: Option<String>,
757 pub credentials: Option<StorageCredentials>,
759 pub timeout_secs: u64,
761}
762
763#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
765pub enum StorageProvider {
766 S3,
768 Gcs,
770 AzureBlob,
772 Minio,
774}
775
776#[derive(Debug, Clone, Serialize, Deserialize)]
778pub enum StorageCredentials {
779 AccessKey {
781 access_key: String,
783 secret_key: String,
785 },
786 ServiceAccount {
788 key_file: String,
790 },
791 InstanceProfile,
793}
794
795#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
801pub enum CallbackEventType {
802 WorkflowStarted,
804 WorkflowCompleted,
806 WorkflowFailed,
808 WorkflowCancelled,
810 TaskStarted,
812 TaskCompleted,
814 TaskFailed,
816 TaskRetry,
818 Custom,
820}
821
822impl CallbackEventType {
823 pub fn as_str(&self) -> &'static str {
825 match self {
826 Self::WorkflowStarted => "workflow.started",
827 Self::WorkflowCompleted => "workflow.completed",
828 Self::WorkflowFailed => "workflow.failed",
829 Self::WorkflowCancelled => "workflow.cancelled",
830 Self::TaskStarted => "task.started",
831 Self::TaskCompleted => "task.completed",
832 Self::TaskFailed => "task.failed",
833 Self::TaskRetry => "task.retry",
834 Self::Custom => "custom",
835 }
836 }
837}
838
839#[derive(Debug, Clone, Serialize, Deserialize)]
841pub struct CallbackPayload {
842 pub event_type: CallbackEventType,
844 pub workflow_id: String,
846 pub execution_id: String,
848 pub task_id: Option<String>,
850 pub timestamp: DateTime<Utc>,
852 pub data: serde_json::Value,
854 pub state_snapshot: Option<serde_json::Value>,
856}
857
858impl CallbackPayload {
859 pub fn for_workflow(
861 event_type: CallbackEventType,
862 workflow_id: impl Into<String>,
863 execution_id: impl Into<String>,
864 data: serde_json::Value,
865 ) -> Self {
866 Self {
867 event_type,
868 workflow_id: workflow_id.into(),
869 execution_id: execution_id.into(),
870 task_id: None,
871 timestamp: Utc::now(),
872 data,
873 state_snapshot: None,
874 }
875 }
876
877 pub fn for_task(
879 event_type: CallbackEventType,
880 workflow_id: impl Into<String>,
881 execution_id: impl Into<String>,
882 task_id: impl Into<String>,
883 data: serde_json::Value,
884 ) -> Self {
885 Self {
886 event_type,
887 workflow_id: workflow_id.into(),
888 execution_id: execution_id.into(),
889 task_id: Some(task_id.into()),
890 timestamp: Utc::now(),
891 data,
892 state_snapshot: None,
893 }
894 }
895
896 pub fn with_state_snapshot(mut self, state: &WorkflowState) -> Self {
898 self.state_snapshot = serde_json::to_value(state).ok();
899 self
900 }
901}
902
903#[async_trait]
905pub trait CallbackHandler: Send + Sync {
906 async fn handle(&self, payload: CallbackPayload) -> Result<()>;
908
909 fn name(&self) -> &str;
911
912 fn is_enabled_for(&self, event_type: CallbackEventType) -> bool;
914}
915
916#[derive(Debug, Clone, Serialize, Deserialize)]
918pub struct CallbackConfig {
919 pub url: String,
921 pub method: HttpMethod,
923 pub headers: HashMap<String, String>,
925 pub auth: Option<HttpAuth>,
927 pub enabled_events: Vec<CallbackEventType>,
929 pub include_state: bool,
931 pub retry: RetryConfig,
933}
934
935#[derive(Debug, Clone, Serialize, Deserialize)]
937pub struct RetryConfig {
938 pub max_retries: u32,
940 pub initial_delay_ms: u64,
942 pub max_delay_ms: u64,
944 pub backoff_multiplier: f64,
946}
947
948impl Default for RetryConfig {
949 fn default() -> Self {
950 Self {
951 max_retries: 3,
952 initial_delay_ms: 1000,
953 max_delay_ms: 30000,
954 backoff_multiplier: 2.0,
955 }
956 }
957}
958
959pub struct HttpCallbackHandler {
961 config: CallbackConfig,
962 #[cfg(feature = "integrations")]
963 client: reqwest::Client,
964}
965
966impl HttpCallbackHandler {
967 #[cfg(feature = "integrations")]
969 pub fn new(config: CallbackConfig) -> Result<Self> {
970 let client = reqwest::Client::builder()
971 .timeout(Duration::from_secs(30))
972 .build()
973 .map_err(|e| {
974 WorkflowError::integration("callback", format!("Failed to create client: {}", e))
975 })?;
976
977 Ok(Self { config, client })
978 }
979
980 pub fn config(&self) -> &CallbackConfig {
982 &self.config
983 }
984}
985
986#[cfg(feature = "integrations")]
987#[async_trait]
988impl CallbackHandler for HttpCallbackHandler {
989 async fn handle(&self, payload: CallbackPayload) -> Result<()> {
990 let mut request = match self.config.method {
991 HttpMethod::Get => self.client.get(&self.config.url),
992 HttpMethod::Post => self.client.post(&self.config.url),
993 HttpMethod::Put => self.client.put(&self.config.url),
994 HttpMethod::Patch => self.client.patch(&self.config.url),
995 HttpMethod::Delete => self.client.delete(&self.config.url),
996 HttpMethod::Head => self.client.head(&self.config.url),
997 HttpMethod::Options => self
998 .client
999 .request(reqwest::Method::OPTIONS, &self.config.url),
1000 };
1001
1002 for (key, value) in &self.config.headers {
1004 request = request.header(key, value);
1005 }
1006
1007 if let Some(auth) = &self.config.auth {
1009 request = match auth {
1010 HttpAuth::Bearer { token } => request.bearer_auth(token),
1011 HttpAuth::Basic { username, password } => {
1012 request.basic_auth(username, Some(password))
1013 }
1014 HttpAuth::ApiKey { header_name, key } => request.header(header_name, key),
1015 HttpAuth::Custom { headers } => {
1016 for (k, v) in headers {
1017 request = request.header(k, v);
1018 }
1019 request
1020 }
1021 };
1022 }
1023
1024 let mut last_error = None;
1025 for attempt in 0..=self.config.retry.max_retries {
1026 if attempt > 0 {
1027 let delay = std::cmp::min(
1028 (self.config.retry.initial_delay_ms as f64
1029 * self.config.retry.backoff_multiplier.powi(attempt as i32))
1030 as u64,
1031 self.config.retry.max_delay_ms,
1032 );
1033 tokio::time::sleep(Duration::from_millis(delay)).await;
1034 }
1035
1036 let req = request
1038 .try_clone()
1039 .ok_or_else(|| WorkflowError::integration("callback", "Failed to clone request"))?;
1040
1041 match req.json(&payload).send().await {
1042 Ok(response) => {
1043 if response.status().is_success() {
1044 debug!(
1045 "Callback delivered successfully: {} -> {}",
1046 payload.event_type.as_str(),
1047 self.config.url
1048 );
1049 return Ok(());
1050 } else if response.status().is_server_error() {
1051 last_error = Some(WorkflowError::integration(
1052 "callback",
1053 format!("Server error: {}", response.status()),
1054 ));
1055 } else {
1056 return Err(WorkflowError::integration(
1057 "callback",
1058 format!("Client error: {}", response.status()),
1059 ));
1060 }
1061 }
1062 Err(e) => {
1063 last_error = Some(WorkflowError::integration(
1064 "callback",
1065 format!("Request failed: {}", e),
1066 ));
1067 }
1068 }
1069 }
1070
1071 Err(last_error.unwrap_or_else(|| {
1072 WorkflowError::integration("callback", "Callback failed after all retries")
1073 }))
1074 }
1075
1076 fn name(&self) -> &str {
1077 "http_callback"
1078 }
1079
1080 fn is_enabled_for(&self, event_type: CallbackEventType) -> bool {
1081 self.config.enabled_events.contains(&event_type)
1082 }
1083}
1084
1085#[derive(Debug, Clone, Serialize, Deserialize)]
1091pub struct WebhookTrigger {
1092 pub id: String,
1094 pub workflow_id: String,
1096 pub secret: Option<String>,
1098 pub allowed_ips: Vec<String>,
1100 pub parameter_mapping: HashMap<String, String>,
1102 pub active: bool,
1104}
1105
1106impl WebhookTrigger {
1107 pub fn new(workflow_id: impl Into<String>) -> Self {
1109 Self {
1110 id: uuid::Uuid::new_v4().to_string(),
1111 workflow_id: workflow_id.into(),
1112 secret: None,
1113 allowed_ips: Vec::new(),
1114 parameter_mapping: HashMap::new(),
1115 active: true,
1116 }
1117 }
1118
1119 pub fn with_secret(mut self, secret: impl Into<String>) -> Self {
1121 self.secret = Some(secret.into());
1122 self
1123 }
1124
1125 pub fn with_allowed_ips(mut self, ips: Vec<String>) -> Self {
1127 self.allowed_ips = ips;
1128 self
1129 }
1130
1131 pub fn with_parameter(
1133 mut self,
1134 webhook_field: impl Into<String>,
1135 workflow_param: impl Into<String>,
1136 ) -> Self {
1137 self.parameter_mapping
1138 .insert(webhook_field.into(), workflow_param.into());
1139 self
1140 }
1141
1142 pub fn validate_signature(&self, payload: &[u8], signature: &str) -> bool {
1144 if let Some(secret) = &self.secret {
1145 let expected = format!("sha256={}", hmac_sha256_hex(payload, secret.as_bytes()));
1146 let signature_lower = signature.to_lowercase();
1148 constant_time_compare(&expected, &signature_lower)
1149 } else {
1150 true }
1152 }
1153}
1154
1155fn constant_time_compare(a: &str, b: &str) -> bool {
1157 if a.len() != b.len() {
1158 return false;
1159 }
1160 let result = a
1161 .bytes()
1162 .zip(b.bytes())
1163 .fold(0u8, |acc, (x, y)| acc | (x ^ y));
1164 result == 0
1165}
1166
1167fn hmac_sha256_hex(data: &[u8], key: &[u8]) -> String {
1172 use hmac::KeyInit;
1173 use hmac::{Hmac, Mac};
1174 use sha2::Sha256;
1175 match <Hmac<Sha256>>::new_from_slice(key) {
1176 Ok(mut mac) => {
1177 mac.update(data);
1178 mac.finalize()
1179 .into_bytes()
1180 .iter()
1181 .map(|b| format!("{b:02x}"))
1182 .collect()
1183 }
1184 Err(_) => String::new(),
1185 }
1186}
1187
1188pub struct WebhookRegistry {
1190 triggers: Arc<RwLock<HashMap<String, WebhookTrigger>>>,
1191}
1192
1193impl WebhookRegistry {
1194 pub fn new() -> Self {
1196 Self {
1197 triggers: Arc::new(RwLock::new(HashMap::new())),
1198 }
1199 }
1200
1201 pub async fn register(&self, trigger: WebhookTrigger) -> String {
1203 let id = trigger.id.clone();
1204 let mut triggers = self.triggers.write().await;
1205 triggers.insert(id.clone(), trigger);
1206 info!("Registered webhook trigger: {}", id);
1207 id
1208 }
1209
1210 pub async fn unregister(&self, trigger_id: &str) -> Option<WebhookTrigger> {
1212 let mut triggers = self.triggers.write().await;
1213 let removed = triggers.remove(trigger_id);
1214 if removed.is_some() {
1215 info!("Unregistered webhook trigger: {}", trigger_id);
1216 }
1217 removed
1218 }
1219
1220 pub async fn get(&self, trigger_id: &str) -> Option<WebhookTrigger> {
1222 let triggers = self.triggers.read().await;
1223 triggers.get(trigger_id).cloned()
1224 }
1225
1226 pub async fn list(&self) -> Vec<WebhookTrigger> {
1228 let triggers = self.triggers.read().await;
1229 triggers.values().cloned().collect()
1230 }
1231
1232 pub async fn find_by_workflow(&self, workflow_id: &str) -> Vec<WebhookTrigger> {
1234 let triggers = self.triggers.read().await;
1235 triggers
1236 .values()
1237 .filter(|t| t.workflow_id == workflow_id && t.active)
1238 .cloned()
1239 .collect()
1240 }
1241}
1242
1243impl Default for WebhookRegistry {
1244 fn default() -> Self {
1245 Self::new()
1246 }
1247}
1248
1249#[derive(Debug, Clone, Serialize, Deserialize)]
1255pub struct ExternalEvent {
1256 pub id: String,
1258 pub event_type: String,
1260 pub source: String,
1262 pub subject: Option<String>,
1264 pub timestamp: DateTime<Utc>,
1266 pub data: serde_json::Value,
1268 pub metadata: HashMap<String, String>,
1270}
1271
1272impl ExternalEvent {
1273 pub fn new(
1275 event_type: impl Into<String>,
1276 source: impl Into<String>,
1277 data: serde_json::Value,
1278 ) -> Self {
1279 Self {
1280 id: uuid::Uuid::new_v4().to_string(),
1281 event_type: event_type.into(),
1282 source: source.into(),
1283 subject: None,
1284 timestamp: Utc::now(),
1285 data,
1286 metadata: HashMap::new(),
1287 }
1288 }
1289
1290 pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
1292 self.subject = Some(subject.into());
1293 self
1294 }
1295
1296 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1298 self.metadata.insert(key.into(), value.into());
1299 self
1300 }
1301
1302 pub fn from_workflow_state(state: &WorkflowState, event_type: impl Into<String>) -> Self {
1304 Self::new(
1305 event_type,
1306 format!("workflow/{}", state.workflow_id),
1307 serde_json::json!({
1308 "workflow_id": state.workflow_id,
1309 "execution_id": state.execution_id,
1310 "status": format!("{:?}", state.status),
1311 }),
1312 )
1313 .with_subject(state.execution_id.clone())
1314 }
1315
1316 pub fn from_task_state(
1318 workflow_id: &str,
1319 task: &TaskState,
1320 event_type: impl Into<String>,
1321 ) -> Self {
1322 Self::new(
1323 event_type,
1324 format!("workflow/{}/task/{}", workflow_id, task.task_id),
1325 serde_json::json!({
1326 "task_id": task.task_id,
1327 "status": format!("{:?}", task.status),
1328 "attempts": task.attempts,
1329 }),
1330 )
1331 .with_subject(task.task_id.clone())
1332 }
1333}
1334
1335#[async_trait]
1337pub trait EventEmitter: Send + Sync {
1338 async fn emit(&self, event: ExternalEvent) -> Result<()>;
1340
1341 async fn emit_batch(&self, events: Vec<ExternalEvent>) -> Result<Vec<Result<()>>> {
1343 let mut results = Vec::with_capacity(events.len());
1344 for event in events {
1345 results.push(self.emit(event).await);
1346 }
1347 Ok(results)
1348 }
1349
1350 fn name(&self) -> &str;
1352}
1353
1354#[derive(Debug, Clone, Serialize, Deserialize)]
1356pub struct EventEmitterConfig {
1357 pub emitter_type: EventEmitterType,
1359 pub target: String,
1361 pub auth: Option<EmitterAuth>,
1363 pub batch_size: usize,
1365 pub flush_interval_ms: u64,
1367}
1368
1369#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1371pub enum EventEmitterType {
1372 Webhook,
1374 MessageQueue,
1376 CloudEvents,
1378 Custom,
1380}
1381
1382#[derive(Debug, Clone, Serialize, Deserialize)]
1384pub enum EmitterAuth {
1385 Bearer {
1387 token: String,
1389 },
1390 ApiKey {
1392 key: String,
1394 },
1395 OAuth2 {
1397 client_id: String,
1399 client_secret: String,
1401 },
1402}
1403
1404pub struct MultiEventEmitter {
1406 emitters: Vec<Arc<dyn EventEmitter>>,
1407}
1408
1409impl MultiEventEmitter {
1410 pub fn new() -> Self {
1412 Self {
1413 emitters: Vec::new(),
1414 }
1415 }
1416
1417 pub fn add_emitter(&mut self, emitter: Arc<dyn EventEmitter>) {
1419 self.emitters.push(emitter);
1420 }
1421
1422 pub fn remove_emitter(&mut self, name: &str) {
1424 self.emitters.retain(|e| e.name() != name);
1425 }
1426
1427 pub fn emitter_count(&self) -> usize {
1429 self.emitters.len()
1430 }
1431}
1432
1433impl Default for MultiEventEmitter {
1434 fn default() -> Self {
1435 Self::new()
1436 }
1437}
1438
1439#[async_trait]
1440impl EventEmitter for MultiEventEmitter {
1441 async fn emit(&self, event: ExternalEvent) -> Result<()> {
1442 let mut errors = Vec::new();
1443
1444 for emitter in &self.emitters {
1445 if let Err(e) = emitter.emit(event.clone()).await {
1446 error!("Failed to emit event via {}: {}", emitter.name(), e);
1447 errors.push(e);
1448 }
1449 }
1450
1451 if errors.is_empty() {
1452 Ok(())
1453 } else {
1454 Err(WorkflowError::integration(
1455 "multi_emitter",
1456 format!("Failed to emit to {} targets", errors.len()),
1457 ))
1458 }
1459 }
1460
1461 fn name(&self) -> &str {
1462 "multi_emitter"
1463 }
1464}
1465
1466pub struct ExternalIntegrationRegistry {
1472 callbacks: Arc<RwLock<Vec<Arc<dyn CallbackHandler>>>>,
1474 webhooks: Arc<WebhookRegistry>,
1476 emitters: Arc<RwLock<MultiEventEmitter>>,
1478}
1479
1480impl ExternalIntegrationRegistry {
1481 pub fn new() -> Self {
1483 Self {
1484 callbacks: Arc::new(RwLock::new(Vec::new())),
1485 webhooks: Arc::new(WebhookRegistry::new()),
1486 emitters: Arc::new(RwLock::new(MultiEventEmitter::new())),
1487 }
1488 }
1489
1490 pub async fn register_callback(&self, handler: Arc<dyn CallbackHandler>) {
1492 let mut callbacks = self.callbacks.write().await;
1493 info!("Registering callback handler: {}", handler.name());
1494 callbacks.push(handler);
1495 }
1496
1497 pub async fn dispatch_callback(&self, payload: CallbackPayload) -> Result<()> {
1499 let callbacks = self.callbacks.read().await;
1500 let mut errors = Vec::new();
1501
1502 for handler in callbacks.iter() {
1503 if handler.is_enabled_for(payload.event_type) {
1504 if let Err(e) = handler.handle(payload.clone()).await {
1505 error!("Callback handler {} failed: {}", handler.name(), e);
1506 errors.push(e);
1507 }
1508 }
1509 }
1510
1511 if errors.is_empty() {
1512 Ok(())
1513 } else {
1514 Err(WorkflowError::integration(
1515 "callbacks",
1516 format!("{} callback handlers failed", errors.len()),
1517 ))
1518 }
1519 }
1520
1521 pub fn webhooks(&self) -> &Arc<WebhookRegistry> {
1523 &self.webhooks
1524 }
1525
1526 pub async fn register_emitter(&self, emitter: Arc<dyn EventEmitter>) {
1528 let mut emitters = self.emitters.write().await;
1529 info!("Registering event emitter: {}", emitter.name());
1530 emitters.add_emitter(emitter);
1531 }
1532
1533 pub async fn emit_event(&self, event: ExternalEvent) -> Result<()> {
1535 let emitters = self.emitters.read().await;
1536 emitters.emit(event).await
1537 }
1538
1539 pub async fn emit_workflow_started(&self, state: &WorkflowState) -> Result<()> {
1541 let event = ExternalEvent::from_workflow_state(state, "workflow.started");
1542 let callback = CallbackPayload::for_workflow(
1543 CallbackEventType::WorkflowStarted,
1544 &state.workflow_id,
1545 &state.execution_id,
1546 serde_json::json!({"status": "started"}),
1547 );
1548
1549 let emit_result = self.emit_event(event).await;
1550 let callback_result = self.dispatch_callback(callback).await;
1551
1552 if emit_result.is_err() || callback_result.is_err() {
1553 warn!("Some integrations failed for workflow.started event");
1554 }
1555
1556 Ok(())
1557 }
1558
1559 pub async fn emit_workflow_completed(&self, state: &WorkflowState) -> Result<()> {
1561 let event = ExternalEvent::from_workflow_state(state, "workflow.completed");
1562 let callback = CallbackPayload::for_workflow(
1563 CallbackEventType::WorkflowCompleted,
1564 &state.workflow_id,
1565 &state.execution_id,
1566 serde_json::json!({"status": "completed"}),
1567 )
1568 .with_state_snapshot(state);
1569
1570 let _ = self.emit_event(event).await;
1571 let _ = self.dispatch_callback(callback).await;
1572
1573 Ok(())
1574 }
1575
1576 pub async fn emit_workflow_failed(&self, state: &WorkflowState, error: &str) -> Result<()> {
1578 let event = ExternalEvent::from_workflow_state(state, "workflow.failed")
1579 .with_metadata("error", error);
1580 let callback = CallbackPayload::for_workflow(
1581 CallbackEventType::WorkflowFailed,
1582 &state.workflow_id,
1583 &state.execution_id,
1584 serde_json::json!({"status": "failed", "error": error}),
1585 )
1586 .with_state_snapshot(state);
1587
1588 let _ = self.emit_event(event).await;
1589 let _ = self.dispatch_callback(callback).await;
1590
1591 Ok(())
1592 }
1593
1594 pub async fn emit_task_started(&self, workflow_id: &str, task: &TaskState) -> Result<()> {
1596 let event = ExternalEvent::from_task_state(workflow_id, task, "task.started");
1597 let callback = CallbackPayload::for_task(
1598 CallbackEventType::TaskStarted,
1599 workflow_id,
1600 "",
1601 &task.task_id,
1602 serde_json::json!({"status": "started"}),
1603 );
1604
1605 let _ = self.emit_event(event).await;
1606 let _ = self.dispatch_callback(callback).await;
1607
1608 Ok(())
1609 }
1610
1611 pub async fn emit_task_completed(&self, workflow_id: &str, task: &TaskState) -> Result<()> {
1613 let event = ExternalEvent::from_task_state(workflow_id, task, "task.completed");
1614 let callback = CallbackPayload::for_task(
1615 CallbackEventType::TaskCompleted,
1616 workflow_id,
1617 "",
1618 &task.task_id,
1619 serde_json::json!({"status": "completed", "output": task.output}),
1620 );
1621
1622 let _ = self.emit_event(event).await;
1623 let _ = self.dispatch_callback(callback).await;
1624
1625 Ok(())
1626 }
1627
1628 pub async fn emit_task_failed(&self, workflow_id: &str, task: &TaskState) -> Result<()> {
1630 let event = ExternalEvent::from_task_state(workflow_id, task, "task.failed")
1631 .with_metadata("error", task.error.as_deref().unwrap_or("unknown"));
1632 let callback = CallbackPayload::for_task(
1633 CallbackEventType::TaskFailed,
1634 workflow_id,
1635 "",
1636 &task.task_id,
1637 serde_json::json!({"status": "failed", "error": task.error}),
1638 );
1639
1640 let _ = self.emit_event(event).await;
1641 let _ = self.dispatch_callback(callback).await;
1642
1643 Ok(())
1644 }
1645}
1646
1647impl Default for ExternalIntegrationRegistry {
1648 fn default() -> Self {
1649 Self::new()
1650 }
1651}
1652
1653#[cfg(test)]
1658mod tests {
1659 use super::*;
1660
1661 #[test]
1662 fn test_http_method_as_str() {
1663 assert_eq!(HttpMethod::Get.as_str(), "GET");
1664 assert_eq!(HttpMethod::Post.as_str(), "POST");
1665 assert_eq!(HttpMethod::Put.as_str(), "PUT");
1666 assert_eq!(HttpMethod::Delete.as_str(), "DELETE");
1667 }
1668
1669 #[test]
1670 fn test_http_response_status_checks() {
1671 let success_response = HttpResponse {
1672 status_code: 200,
1673 headers: HashMap::new(),
1674 body: None,
1675 response_time_ms: 100,
1676 is_success: true,
1677 };
1678 assert!(success_response.is_success());
1679 assert!(!success_response.is_client_error());
1680 assert!(!success_response.is_server_error());
1681
1682 let client_error = HttpResponse {
1683 status_code: 404,
1684 headers: HashMap::new(),
1685 body: None,
1686 response_time_ms: 50,
1687 is_success: false,
1688 };
1689 assert!(!client_error.is_success());
1690 assert!(client_error.is_client_error());
1691
1692 let server_error = HttpResponse {
1693 status_code: 500,
1694 headers: HashMap::new(),
1695 body: None,
1696 response_time_ms: 75,
1697 is_success: false,
1698 };
1699 assert!(!server_error.is_success());
1700 assert!(server_error.is_server_error());
1701 }
1702
1703 #[test]
1704 fn test_queue_message_builder() {
1705 let msg = QueueMessage::new("test-topic", serde_json::json!({"key": "value"}))
1706 .with_correlation_id("corr-123")
1707 .with_reply_to("reply-topic")
1708 .with_priority(8)
1709 .with_header("custom", "header");
1710
1711 assert_eq!(msg.topic, "test-topic");
1712 assert_eq!(msg.correlation_id, Some("corr-123".to_string()));
1713 assert_eq!(msg.reply_to, Some("reply-topic".to_string()));
1714 assert_eq!(msg.priority, 8);
1715 assert_eq!(msg.headers.get("custom"), Some(&"header".to_string()));
1716 }
1717
1718 #[test]
1719 fn test_query_result() {
1720 let result = QueryResult::empty();
1721 assert!(result.is_empty());
1722 assert_eq!(result.row_count(), 0);
1723
1724 let result_with_data = QueryResult {
1725 rows_affected: 1,
1726 columns: vec!["id".to_string(), "name".to_string()],
1727 rows: vec![vec![serde_json::json!(1), serde_json::json!("test")]],
1728 execution_time_ms: 10,
1729 };
1730 assert!(!result_with_data.is_empty());
1731 assert_eq!(result_with_data.row_count(), 1);
1732 }
1733
1734 #[test]
1735 fn test_callback_event_type_as_str() {
1736 assert_eq!(
1737 CallbackEventType::WorkflowStarted.as_str(),
1738 "workflow.started"
1739 );
1740 assert_eq!(CallbackEventType::TaskCompleted.as_str(), "task.completed");
1741 assert_eq!(CallbackEventType::Custom.as_str(), "custom");
1742 }
1743
1744 #[test]
1745 fn test_callback_payload_creation() {
1746 let workflow_payload = CallbackPayload::for_workflow(
1747 CallbackEventType::WorkflowStarted,
1748 "wf-1",
1749 "exec-1",
1750 serde_json::json!({}),
1751 );
1752 assert_eq!(workflow_payload.workflow_id, "wf-1");
1753 assert_eq!(workflow_payload.execution_id, "exec-1");
1754 assert!(workflow_payload.task_id.is_none());
1755
1756 let task_payload = CallbackPayload::for_task(
1757 CallbackEventType::TaskStarted,
1758 "wf-1",
1759 "exec-1",
1760 "task-1",
1761 serde_json::json!({}),
1762 );
1763 assert_eq!(task_payload.task_id, Some("task-1".to_string()));
1764 }
1765
1766 #[test]
1767 fn test_webhook_trigger_creation() {
1768 let trigger = WebhookTrigger::new("workflow-1")
1769 .with_secret("my-secret")
1770 .with_allowed_ips(vec!["192.168.1.1".to_string()])
1771 .with_parameter("payload.id", "workflow_param");
1772
1773 assert_eq!(trigger.workflow_id, "workflow-1");
1774 assert!(trigger.secret.is_some());
1775 assert_eq!(trigger.allowed_ips.len(), 1);
1776 assert!(trigger.parameter_mapping.contains_key("payload.id"));
1777 }
1778
1779 #[tokio::test]
1780 async fn test_webhook_registry() {
1781 let registry = WebhookRegistry::new();
1782
1783 let trigger = WebhookTrigger::new("workflow-1");
1784 let id = registry.register(trigger).await;
1785
1786 assert!(registry.get(&id).await.is_some());
1787
1788 let triggers = registry.list().await;
1789 assert_eq!(triggers.len(), 1);
1790
1791 let workflow_triggers = registry.find_by_workflow("workflow-1").await;
1792 assert_eq!(workflow_triggers.len(), 1);
1793
1794 assert!(registry.unregister(&id).await.is_some());
1795 assert!(registry.get(&id).await.is_none());
1796 }
1797
1798 #[test]
1799 fn test_external_event_creation() {
1800 let event = ExternalEvent::new(
1801 "test.event",
1802 "test-source",
1803 serde_json::json!({"data": "value"}),
1804 )
1805 .with_subject("subject-1")
1806 .with_metadata("key", "value");
1807
1808 assert_eq!(event.event_type, "test.event");
1809 assert_eq!(event.source, "test-source");
1810 assert_eq!(event.subject, Some("subject-1".to_string()));
1811 assert_eq!(event.metadata.get("key"), Some(&"value".to_string()));
1812 }
1813
1814 #[test]
1815 fn test_retry_config_default() {
1816 let config = RetryConfig::default();
1817 assert_eq!(config.max_retries, 3);
1818 assert_eq!(config.initial_delay_ms, 1000);
1819 assert_eq!(config.max_delay_ms, 30000);
1820 assert!((config.backoff_multiplier - 2.0).abs() < f64::EPSILON);
1821 }
1822
1823 #[test]
1824 fn test_http_client_config_default() {
1825 let config = HttpClientConfig::default();
1826 assert_eq!(config.timeout_secs, 30);
1827 assert_eq!(config.max_retries, 3);
1828 assert!(config.base_url.is_empty());
1829 }
1830
1831 #[test]
1832 fn test_database_config_default() {
1833 let config = DatabaseConfig::default();
1834 assert_eq!(config.db_type, DatabaseType::PostgreSql);
1835 assert_eq!(config.pool_min, 1);
1836 assert_eq!(config.pool_max, 10);
1837 }
1838
1839 #[tokio::test]
1840 async fn test_multi_event_emitter() {
1841 let emitter = MultiEventEmitter::new();
1842 assert_eq!(emitter.emitter_count(), 0);
1843 }
1844
1845 #[tokio::test]
1846 async fn test_external_integration_registry() {
1847 let registry = ExternalIntegrationRegistry::new();
1848
1849 let trigger = WebhookTrigger::new("workflow-1");
1850 let _ = registry.webhooks().register(trigger).await;
1851
1852 let triggers = registry.webhooks().list().await;
1853 assert_eq!(triggers.len(), 1);
1854 }
1855
1856 #[test]
1857 fn test_constant_time_compare() {
1858 assert!(constant_time_compare("test", "test"));
1859 assert!(!constant_time_compare("test", "Test"));
1860 assert!(!constant_time_compare("test", "test1"));
1861 }
1862}