Skip to main content

swf_core/models/
event.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::HashMap;
4
5/// Represents the configuration of an event consumption strategy
6#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
7pub struct EventConsumptionStrategyDefinition {
8    /// Gets/sets a list containing all the events that must be consumed, if any
9    #[serde(skip_serializing_if = "Option::is_none")]
10    pub all: Option<Vec<EventFilterDefinition>>,
11
12    /// Gets/sets a list containing any of the events to consume, if any
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub any: Option<Vec<EventFilterDefinition>>,
15
16    /// Gets/sets the single event to consume
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub one: Option<EventFilterDefinition>,
19
20    /// Gets/sets the consumption strategy, if any, that defines the events that must be consumed to stop listening
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub until: Option<Box<OneOfEventConsumptionStrategyDefinitionOrExpression>>,
23}
24
25/// Represents the configuration of an event filter
26#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
27pub struct EventFilterDefinition {
28    /// Gets/sets a name/value mapping of the attributes filtered events must define. Supports both regular expressions and runtime expressions
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub with: Option<HashMap<String, Value>>,
31
32    /// Gets/sets a name/definition mapping of the correlation to attempt when filtering events.
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub correlate: Option<HashMap<String, CorrelationKeyDefinition>>,
35}
36
37/// Represents the definition of an event correlation key
38#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
39pub struct CorrelationKeyDefinition {
40    /// Gets/sets a runtime expression used to extract the correlation key value from events
41    pub from: String,
42
43    /// Gets/sets a constant or a runtime expression, if any, used to determine whether or not the extracted correlation key value matches expectations and should be correlated. If not set, the first extracted value will be used as the correlation key's expectation
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub expect: Option<String>,
46}
47impl CorrelationKeyDefinition {
48    pub fn new(from: &str, expect: Option<String>) -> Self {
49        Self {
50            from: from.to_string(),
51            expect,
52        }
53    }
54}
55
56/// Represents the definition of an event
57#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
58pub struct EventDefinition {
59    /// Gets/sets the unique event identifier
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub id: Option<String>,
62
63    /// Gets/sets the event source (URI template or runtime expression)
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub source: Option<String>,
66
67    /// Gets/sets the event type
68    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
69    pub type_: Option<String>,
70
71    /// Gets/sets the event time (ISO 8601 date-time string or runtime expression)
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub time: Option<String>,
74
75    /// Gets/sets the event subject
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub subject: Option<String>,
78
79    /// Gets/sets the data content type
80    #[serde(rename = "datacontenttype", skip_serializing_if = "Option::is_none")]
81    pub data_content_type: Option<String>,
82
83    /// Gets/sets the data schema (URI template or runtime expression)
84    #[serde(rename = "dataschema", skip_serializing_if = "Option::is_none")]
85    pub data_schema: Option<String>,
86
87    /// Gets/sets the event data payload
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub data: Option<Value>,
90
91    /// Gets/sets a key/value mapping of the attributes of the configured event
92    #[serde(default)]
93    pub with: HashMap<String, Value>,
94}
95impl EventDefinition {
96    pub fn new(with: HashMap<String, Value>) -> Self {
97        Self {
98            with,
99            ..Default::default()
100        }
101    }
102}
103
104/// Represents a value that can be either a EventConsumptionStrategyDefinition, a runtime expression, or a boolean (for disabled)
105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
106#[serde(untagged)]
107pub enum OneOfEventConsumptionStrategyDefinitionOrExpression {
108    /// Variant holding an EventConsumptionStrategyDefinition
109    Strategy(EventConsumptionStrategyDefinition),
110    /// Variant holding a runtime expression string
111    Expression(String),
112    /// Variant holding a boolean (true = continue, false = disabled)
113    Bool(bool),
114}
115impl Default for OneOfEventConsumptionStrategyDefinitionOrExpression {
116    fn default() -> Self {
117        OneOfEventConsumptionStrategyDefinitionOrExpression::Expression(String::default())
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_event_definition_deserialize() {
127        let json = r#"{
128            "id": "evt-123",
129            "source": "https://example.com/events",
130            "type": "com.example.event",
131            "time": "2025-01-01T00:00:00Z",
132            "subject": "user/123",
133            "datacontenttype": "application/json",
134            "dataschema": "https://example.com/schema"
135        }"#;
136        let event: EventDefinition = serde_json::from_str(json).unwrap();
137        assert_eq!(event.id, Some("evt-123".to_string()));
138        assert_eq!(event.source, Some("https://example.com/events".to_string()));
139        assert_eq!(event.type_, Some("com.example.event".to_string()));
140        assert_eq!(event.subject, Some("user/123".to_string()));
141        assert_eq!(
142            event.data_content_type,
143            Some("application/json".to_string())
144        );
145    }
146
147    #[test]
148    fn test_event_definition_roundtrip() {
149        let json = r#"{
150            "id": "evt-123",
151            "source": "https://example.com/events",
152            "type": "com.example.event"
153        }"#;
154        let event: EventDefinition = serde_json::from_str(json).unwrap();
155        let serialized = serde_json::to_string(&event).unwrap();
156        let deserialized: EventDefinition = serde_json::from_str(&serialized).unwrap();
157        assert_eq!(event, deserialized);
158    }
159
160    #[test]
161    fn test_event_filter_deserialize() {
162        let json = r#"{
163            "with": {"type": "com.example.event", "source": "https://example.com"},
164            "correlate": {
165                "userId": {"from": "${ .userId }", "expect": "user-123"}
166            }
167        }"#;
168        let filter: EventFilterDefinition = serde_json::from_str(json).unwrap();
169        assert!(filter.with.is_some());
170        assert!(filter.correlate.is_some());
171        let corr = filter.correlate.unwrap();
172        assert_eq!(corr.get("userId").unwrap().from, "${ .userId }");
173    }
174
175    #[test]
176    fn test_correlation_key_deserialize() {
177        let json = r#"{"from": "${ .orderId }", "expect": "order-456"}"#;
178        let key: CorrelationKeyDefinition = serde_json::from_str(json).unwrap();
179        assert_eq!(key.from, "${ .orderId }");
180        assert_eq!(key.expect, Some("order-456".to_string()));
181    }
182
183    #[test]
184    fn test_correlation_key_without_expect() {
185        let json = r#"{"from": "${ .userId }"}"#;
186        let key: CorrelationKeyDefinition = serde_json::from_str(json).unwrap();
187        assert_eq!(key.from, "${ .userId }");
188        assert!(key.expect.is_none());
189    }
190
191    #[test]
192    fn test_consumption_strategy_all() {
193        let json = r#"{
194            "all": [
195                {"with": {"type": "event1"}},
196                {"with": {"type": "event2"}}
197            ]
198        }"#;
199        let strategy: EventConsumptionStrategyDefinition = serde_json::from_str(json).unwrap();
200        assert!(strategy.all.is_some());
201        assert_eq!(strategy.all.unwrap().len(), 2);
202        assert!(strategy.any.is_none());
203        assert!(strategy.one.is_none());
204    }
205
206    #[test]
207    fn test_consumption_strategy_any() {
208        let json = r#"{
209            "any": [
210                {"with": {"type": "event1"}},
211                {"with": {"type": "event2"}}
212            ]
213        }"#;
214        let strategy: EventConsumptionStrategyDefinition = serde_json::from_str(json).unwrap();
215        assert!(strategy.any.is_some());
216        assert!(strategy.all.is_none());
217    }
218
219    #[test]
220    fn test_consumption_strategy_one() {
221        let json = r#"{
222            "one": {"with": {"type": "event1"}}
223        }"#;
224        let strategy: EventConsumptionStrategyDefinition = serde_json::from_str(json).unwrap();
225        assert!(strategy.one.is_some());
226        assert!(strategy.all.is_none());
227        assert!(strategy.any.is_none());
228    }
229
230    #[test]
231    fn test_consumption_strategy_with_until_strategy() {
232        let json = r#"{
233            "any": [{"with": {"type": "event1"}}],
234            "until": {"any": [{"with": {"type": "stop-event"}}]}
235        }"#;
236        let strategy: EventConsumptionStrategyDefinition = serde_json::from_str(json).unwrap();
237        assert!(strategy.until.is_some());
238        match *strategy.until.unwrap() {
239            OneOfEventConsumptionStrategyDefinitionOrExpression::Strategy(s) => {
240                assert!(s.any.is_some());
241            }
242            _ => panic!("Expected Strategy variant"),
243        }
244    }
245
246    #[test]
247    fn test_consumption_strategy_with_until_expression() {
248        let json = r#"{
249            "any": [{"with": {"type": "event1"}}],
250            "until": "${ .count >= 5 }"
251        }"#;
252        let strategy: EventConsumptionStrategyDefinition = serde_json::from_str(json).unwrap();
253        assert!(strategy.until.is_some());
254        match *strategy.until.unwrap() {
255            OneOfEventConsumptionStrategyDefinitionOrExpression::Expression(expr) => {
256                assert_eq!(expr, "${ .count >= 5 }");
257            }
258            _ => panic!("Expected Expression variant"),
259        }
260    }
261
262    #[test]
263    fn test_consumption_strategy_with_until_false() {
264        let json = r#"{
265            "any": [{"with": {"type": "event1"}}],
266            "until": false
267        }"#;
268        let strategy: EventConsumptionStrategyDefinition = serde_json::from_str(json).unwrap();
269        assert!(strategy.until.is_some());
270        match *strategy.until.unwrap() {
271            OneOfEventConsumptionStrategyDefinitionOrExpression::Bool(b) => {
272                assert!(!b);
273            }
274            _ => panic!("Expected Bool variant"),
275        }
276    }
277
278    #[test]
279    fn test_event_consumption_roundtrip() {
280        let json = r#"{
281            "all": [{"with": {"type": "event1"}}],
282            "until": false
283        }"#;
284        let strategy: EventConsumptionStrategyDefinition = serde_json::from_str(json).unwrap();
285        let serialized = serde_json::to_string(&strategy).unwrap();
286        let deserialized: EventConsumptionStrategyDefinition =
287            serde_json::from_str(&serialized).unwrap();
288        assert_eq!(strategy, deserialized);
289    }
290
291    #[test]
292    fn test_event_with_additional_properties() {
293        let json = r#"{
294            "id": "evt-123",
295            "type": "com.example.event",
296            "customField": "customValue"
297        }"#;
298        let event: EventDefinition = serde_json::from_str(json).unwrap();
299        assert_eq!(event.id, Some("evt-123".to_string()));
300        assert_eq!(event.type_, Some("com.example.event".to_string()));
301    }
302
303    // Additional tests matching Go SDK's task_event_test.go patterns
304
305    #[test]
306    fn test_emit_event_properties_full() {
307        // Matches Go SDK's TestEmitTask_UnmarshalJSON event properties
308        let json = r#"{
309            "id": "event-id",
310            "source": "http://example.com/source",
311            "type": "example.event.type",
312            "time": "2023-01-01T00:00:00Z",
313            "subject": "example.subject",
314            "datacontenttype": "application/json",
315            "dataschema": "http://example.com/schema",
316            "extra": "value"
317        }"#;
318        let event: EventDefinition = serde_json::from_str(json).unwrap();
319        assert_eq!(event.id, Some("event-id".to_string()));
320        assert_eq!(event.source, Some("http://example.com/source".to_string()));
321        assert_eq!(event.type_, Some("example.event.type".to_string()));
322        assert_eq!(event.time, Some("2023-01-01T00:00:00Z".to_string()));
323        assert_eq!(event.subject, Some("example.subject".to_string()));
324        assert_eq!(
325            event.data_content_type,
326            Some("application/json".to_string())
327        );
328        assert_eq!(
329            event.data_schema,
330            Some("http://example.com/schema".to_string())
331        );
332    }
333
334    #[test]
335    fn test_event_filter_roundtrip() {
336        let json = r#"{
337            "with": {"type": "com.example.event", "source": "http://example.com/source"},
338            "correlate": {
339                "orderId": {"from": "${ .orderId }", "expect": "order-123"}
340            }
341        }"#;
342        let filter: EventFilterDefinition = serde_json::from_str(json).unwrap();
343        let serialized = serde_json::to_string(&filter).unwrap();
344        let deserialized: EventFilterDefinition = serde_json::from_str(&serialized).unwrap();
345        assert_eq!(filter, deserialized);
346    }
347
348    #[test]
349    fn test_event_definition_roundtrip_full() {
350        // Full roundtrip test with all fields
351        let json = r#"{
352            "id": "evt-456",
353            "source": "https://example.com/events",
354            "type": "com.example.event",
355            "time": "2025-06-15T10:30:00Z",
356            "subject": "user/456",
357            "datacontenttype": "application/json",
358            "dataschema": "https://example.com/schema"
359        }"#;
360        let event: EventDefinition = serde_json::from_str(json).unwrap();
361        let serialized = serde_json::to_string(&event).unwrap();
362        let deserialized: EventDefinition = serde_json::from_str(&serialized).unwrap();
363        assert_eq!(event, deserialized);
364    }
365
366    #[test]
367    fn test_consumption_until_disabled() {
368        // Matches Go SDK's TestEventConsumptionUntil_MarshalJSON "Until Disabled"
369        // until: false means disabled
370        let json = r#"{
371            "any": [{"with": {"type": "event1"}}],
372            "until": false
373        }"#;
374        let strategy: EventConsumptionStrategyDefinition = serde_json::from_str(json).unwrap();
375        match *strategy.until.unwrap() {
376            OneOfEventConsumptionStrategyDefinitionOrExpression::Bool(b) => {
377                assert!(!b);
378            }
379            _ => panic!("Expected Bool(false) variant"),
380        }
381    }
382
383    #[test]
384    fn test_consumption_until_expression_string() {
385        // Matches Go SDK's TestEventConsumptionUntil_MarshalJSON "Until Condition Set"
386        let json = r#"{
387            "any": [{"with": {"type": "event1"}}],
388            "until": "workflow.data.condition == true"
389        }"#;
390        let strategy: EventConsumptionStrategyDefinition = serde_json::from_str(json).unwrap();
391        match *strategy.until.unwrap() {
392            OneOfEventConsumptionStrategyDefinitionOrExpression::Expression(expr) => {
393                assert_eq!(expr, "workflow.data.condition == true");
394            }
395            _ => panic!("Expected Expression variant"),
396        }
397    }
398
399    #[test]
400    fn test_consumption_until_nested_strategy() {
401        // Matches Go SDK's TestEventConsumptionUntil_MarshalJSON "Until Nested Strategy"
402        let json = r#"{
403            "any": [{"with": {"type": "event1"}}],
404            "until": {"one": {"with": {"type": "example.event.type"}}}
405        }"#;
406        let strategy: EventConsumptionStrategyDefinition = serde_json::from_str(json).unwrap();
407        match *strategy.until.unwrap() {
408            OneOfEventConsumptionStrategyDefinitionOrExpression::Strategy(s) => {
409                assert!(s.one.is_some());
410                assert!(s.all.is_none());
411                assert!(s.any.is_none());
412            }
413            _ => panic!("Expected Strategy variant"),
414        }
415    }
416
417    #[test]
418    fn test_correlation_key_roundtrip() {
419        let json = r#"{"from": "${ .orderId }", "expect": "order-789"}"#;
420        let key: CorrelationKeyDefinition = serde_json::from_str(json).unwrap();
421        let serialized = serde_json::to_string(&key).unwrap();
422        let deserialized: CorrelationKeyDefinition = serde_json::from_str(&serialized).unwrap();
423        assert_eq!(key, deserialized);
424    }
425
426    #[test]
427    fn test_event_filter_minimal() {
428        // Only "with" field, no correlate
429        let json = r#"{"with": {"type": "com.example.event"}}"#;
430        let filter: EventFilterDefinition = serde_json::from_str(json).unwrap();
431        assert!(filter.with.is_some());
432        assert!(filter.correlate.is_none());
433    }
434}