1use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc};
7use serde::{Deserialize, Deserializer, Serialize};
8
9use crate::error::ErrorObject;
10
11fn deserialize_timestamp<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
13where
14 D: Deserializer<'de>,
15{
16 use serde::de::{self, Visitor};
17
18 struct TimestampVisitor;
19
20 impl<'de> Visitor<'de> for TimestampVisitor {
21 type Value = Option<i64>;
22
23 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
24 formatter.write_str("an integer timestamp or ISO 8601 string")
25 }
26
27 fn visit_none<E>(self) -> Result<Self::Value, E>
28 where
29 E: de::Error,
30 {
31 Ok(None)
32 }
33
34 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
35 where
36 D: Deserializer<'de>,
37 {
38 deserializer.deserialize_any(TimestampValueVisitor)
39 }
40
41 fn visit_unit<E>(self) -> Result<Self::Value, E>
42 where
43 E: de::Error,
44 {
45 Ok(None)
46 }
47 }
48
49 struct TimestampValueVisitor;
50
51 impl<'de> Visitor<'de> for TimestampValueVisitor {
52 type Value = Option<i64>;
53
54 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
55 formatter
56 .write_str("an integer timestamp, floating point timestamp, or ISO 8601 string")
57 }
58
59 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
60 where
61 E: de::Error,
62 {
63 Ok(Some(value))
64 }
65
66 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
67 where
68 E: de::Error,
69 {
70 Ok(Some(value as i64))
71 }
72
73 fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
74 where
75 E: de::Error,
76 {
77 Ok(Some((value * 1000.0).round() as i64))
82 }
83
84 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
85 where
86 E: de::Error,
87 {
88 parse_iso8601_to_millis(value).map(Some).map_err(|e| {
90 de::Error::custom(format!("invalid timestamp string '{}': {}", value, e))
91 })
92 }
93 }
94
95 deserializer.deserialize_option(TimestampVisitor)
96}
97
98fn parse_iso8601_to_millis(s: &str) -> Result<i64, String> {
100 let normalized = s.replace(' ', "T");
102
103 if let Ok(dt) = DateTime::parse_from_rfc3339(&normalized) {
105 return Ok(dt.timestamp_millis());
106 }
107
108 if let Ok(dt) = DateTime::<FixedOffset>::parse_from_str(&normalized, "%Y-%m-%dT%H:%M:%S%.f%:z")
110 {
111 return Ok(dt.timestamp_millis());
112 }
113
114 if let Ok(dt) = DateTime::<FixedOffset>::parse_from_str(&normalized, "%Y-%m-%dT%H:%M:%S%:z") {
115 return Ok(dt.timestamp_millis());
116 }
117
118 if let Ok(naive) = NaiveDateTime::parse_from_str(&normalized, "%Y-%m-%dT%H:%M:%S%.f") {
120 return Ok(Utc.from_utc_datetime(&naive).timestamp_millis());
121 }
122
123 if let Ok(naive) = NaiveDateTime::parse_from_str(&normalized, "%Y-%m-%dT%H:%M:%S") {
124 return Ok(Utc.from_utc_datetime(&naive).timestamp_millis());
125 }
126
127 Err("unable to parse as ISO 8601 datetime".to_string())
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct Operation {
165 #[serde(rename = "Id", alias = "OperationId")]
167 pub operation_id: String,
168
169 #[serde(rename = "Type", alias = "OperationType")]
171 pub operation_type: OperationType,
172
173 #[serde(rename = "Status")]
175 pub status: OperationStatus,
176
177 #[serde(rename = "Result", skip_serializing_if = "Option::is_none")]
179 pub result: Option<String>,
180
181 #[serde(rename = "Error", skip_serializing_if = "Option::is_none")]
183 pub error: Option<ErrorObject>,
184
185 #[serde(rename = "ParentId", skip_serializing_if = "Option::is_none")]
187 pub parent_id: Option<String>,
188
189 #[serde(rename = "Name", skip_serializing_if = "Option::is_none")]
191 pub name: Option<String>,
192
193 #[serde(rename = "SubType", skip_serializing_if = "Option::is_none")]
196 pub sub_type: Option<String>,
197
198 #[serde(
200 rename = "StartTimestamp",
201 skip_serializing_if = "Option::is_none",
202 default,
203 deserialize_with = "deserialize_timestamp"
204 )]
205 pub start_timestamp: Option<i64>,
206
207 #[serde(
209 rename = "EndTimestamp",
210 skip_serializing_if = "Option::is_none",
211 default,
212 deserialize_with = "deserialize_timestamp"
213 )]
214 pub end_timestamp: Option<i64>,
215
216 #[serde(rename = "ExecutionDetails", skip_serializing_if = "Option::is_none")]
218 pub execution_details: Option<ExecutionDetails>,
219
220 #[serde(rename = "StepDetails", skip_serializing_if = "Option::is_none")]
222 pub step_details: Option<StepDetails>,
223
224 #[serde(rename = "WaitDetails", skip_serializing_if = "Option::is_none")]
226 pub wait_details: Option<WaitDetails>,
227
228 #[serde(rename = "CallbackDetails", skip_serializing_if = "Option::is_none")]
230 pub callback_details: Option<CallbackDetails>,
231
232 #[serde(
234 rename = "ChainedInvokeDetails",
235 skip_serializing_if = "Option::is_none"
236 )]
237 pub chained_invoke_details: Option<ChainedInvokeDetails>,
238
239 #[serde(rename = "ContextDetails", skip_serializing_if = "Option::is_none")]
241 pub context_details: Option<ContextDetails>,
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct ExecutionDetails {
247 #[serde(rename = "InputPayload", skip_serializing_if = "Option::is_none")]
249 pub input_payload: Option<String>,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct StepDetails {
255 #[serde(rename = "Result", skip_serializing_if = "Option::is_none")]
257 pub result: Option<String>,
258 #[serde(rename = "Attempt", skip_serializing_if = "Option::is_none")]
260 pub attempt: Option<u32>,
261 #[serde(
263 rename = "NextAttemptTimestamp",
264 skip_serializing_if = "Option::is_none",
265 default,
266 deserialize_with = "deserialize_timestamp"
267 )]
268 pub next_attempt_timestamp: Option<i64>,
269 #[serde(rename = "Error", skip_serializing_if = "Option::is_none")]
271 pub error: Option<ErrorObject>,
272 #[serde(rename = "Payload", skip_serializing_if = "Option::is_none")]
275 pub payload: Option<String>,
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct WaitDetails {
281 #[serde(
283 rename = "ScheduledEndTimestamp",
284 skip_serializing_if = "Option::is_none",
285 default,
286 deserialize_with = "deserialize_timestamp"
287 )]
288 pub scheduled_end_timestamp: Option<i64>,
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
293pub struct CallbackDetails {
294 #[serde(rename = "CallbackId", skip_serializing_if = "Option::is_none")]
296 pub callback_id: Option<String>,
297 #[serde(rename = "Result", skip_serializing_if = "Option::is_none")]
299 pub result: Option<String>,
300 #[serde(rename = "Error", skip_serializing_if = "Option::is_none")]
302 pub error: Option<ErrorObject>,
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct ChainedInvokeDetails {
308 #[serde(rename = "Result", skip_serializing_if = "Option::is_none")]
310 pub result: Option<String>,
311 #[serde(rename = "Error", skip_serializing_if = "Option::is_none")]
313 pub error: Option<ErrorObject>,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct ContextDetails {
319 #[serde(rename = "Result", skip_serializing_if = "Option::is_none")]
321 pub result: Option<String>,
322 #[serde(rename = "ReplayChildren", skip_serializing_if = "Option::is_none")]
324 pub replay_children: Option<bool>,
325 #[serde(rename = "Error", skip_serializing_if = "Option::is_none")]
327 pub error: Option<ErrorObject>,
328}
329
330impl Operation {
331 pub fn new(operation_id: impl Into<String>, operation_type: OperationType) -> Self {
333 Self {
334 operation_id: operation_id.into(),
335 operation_type,
336 status: OperationStatus::Started,
337 result: None,
338 error: None,
339 parent_id: None,
340 name: None,
341 sub_type: None,
342 start_timestamp: None,
343 end_timestamp: None,
344 execution_details: None,
345 step_details: None,
346 wait_details: None,
347 callback_details: None,
348 chained_invoke_details: None,
349 context_details: None,
350 }
351 }
352
353 pub fn with_parent_id(mut self, parent_id: impl Into<String>) -> Self {
355 self.parent_id = Some(parent_id.into());
356 self
357 }
358
359 pub fn with_name(mut self, name: impl Into<String>) -> Self {
361 self.name = Some(name.into());
362 self
363 }
364
365 pub fn with_sub_type(mut self, sub_type: impl Into<String>) -> Self {
368 self.sub_type = Some(sub_type.into());
369 self
370 }
371
372 pub fn is_completed(&self) -> bool {
374 matches!(
375 self.status,
376 OperationStatus::Succeeded
377 | OperationStatus::Failed
378 | OperationStatus::Cancelled
379 | OperationStatus::TimedOut
380 | OperationStatus::Stopped
381 )
382 }
383
384 pub fn is_succeeded(&self) -> bool {
386 matches!(self.status, OperationStatus::Succeeded)
387 }
388
389 pub fn is_failed(&self) -> bool {
391 matches!(
392 self.status,
393 OperationStatus::Failed | OperationStatus::Cancelled | OperationStatus::TimedOut
394 )
395 }
396
397 pub fn get_result(&self) -> Option<&str> {
399 match self.operation_type {
401 OperationType::Step => {
402 if let Some(ref details) = self.step_details {
403 if details.result.is_some() {
404 return details.result.as_deref();
405 }
406 }
407 }
408 OperationType::Callback => {
409 if let Some(ref details) = self.callback_details {
410 if details.result.is_some() {
411 return details.result.as_deref();
412 }
413 }
414 }
415 OperationType::Invoke => {
416 if let Some(ref details) = self.chained_invoke_details {
417 if details.result.is_some() {
418 return details.result.as_deref();
419 }
420 }
421 }
422 OperationType::Context => {
423 if let Some(ref details) = self.context_details {
424 if details.result.is_some() {
425 return details.result.as_deref();
426 }
427 }
428 }
429 _ => {}
430 }
431 self.result.as_deref()
433 }
434
435 pub fn get_retry_payload(&self) -> Option<&str> {
444 if self.operation_type == OperationType::Step {
445 if let Some(ref details) = self.step_details {
446 return details.payload.as_deref();
447 }
448 }
449 None
450 }
451
452 pub fn get_attempt(&self) -> Option<u32> {
458 if self.operation_type == OperationType::Step {
459 if let Some(ref details) = self.step_details {
460 return details.attempt;
461 }
462 }
463 None
464 }
465}
466
467#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
489#[repr(u8)]
490pub enum OperationType {
491 #[serde(rename = "EXECUTION")]
493 Execution = 0,
494 #[serde(rename = "STEP")]
496 Step = 1,
497 #[serde(rename = "WAIT")]
499 Wait = 2,
500 #[serde(rename = "CALLBACK")]
502 Callback = 3,
503 #[serde(rename = "INVOKE")]
505 Invoke = 4,
506 #[serde(rename = "CONTEXT")]
508 Context = 5,
509}
510
511impl std::fmt::Display for OperationType {
512 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
513 match self {
514 Self::Execution => write!(f, "Execution"),
515 Self::Step => write!(f, "Step"),
516 Self::Wait => write!(f, "Wait"),
517 Self::Callback => write!(f, "Callback"),
518 Self::Invoke => write!(f, "Invoke"),
519 Self::Context => write!(f, "Context"),
520 }
521 }
522}
523
524#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
550#[repr(u8)]
551pub enum OperationStatus {
552 #[serde(rename = "STARTED")]
554 Started = 0,
555 #[serde(rename = "PENDING")]
558 Pending = 1,
559 #[serde(rename = "READY")]
562 Ready = 2,
563 #[serde(rename = "SUCCEEDED")]
565 Succeeded = 3,
566 #[serde(rename = "FAILED")]
568 Failed = 4,
569 #[serde(rename = "CANCELLED")]
571 Cancelled = 5,
572 #[serde(rename = "TIMED_OUT")]
574 TimedOut = 6,
575 #[serde(rename = "STOPPED")]
577 Stopped = 7,
578}
579
580impl OperationStatus {
581 pub fn is_terminal(&self) -> bool {
583 !matches!(self, Self::Started | Self::Pending | Self::Ready)
584 }
585
586 pub fn is_success(&self) -> bool {
588 matches!(self, Self::Succeeded)
589 }
590
591 pub fn is_failure(&self) -> bool {
593 matches!(
594 self,
595 Self::Failed | Self::Cancelled | Self::TimedOut | Self::Stopped
596 )
597 }
598
599 pub fn is_pending(&self) -> bool {
602 matches!(self, Self::Pending)
603 }
604
605 pub fn is_ready(&self) -> bool {
608 matches!(self, Self::Ready)
609 }
610
611 pub fn is_resumable(&self) -> bool {
615 matches!(self, Self::Started | Self::Pending | Self::Ready)
616 }
617}
618
619impl std::fmt::Display for OperationStatus {
620 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
621 match self {
622 Self::Started => write!(f, "Started"),
623 Self::Pending => write!(f, "Pending"),
624 Self::Ready => write!(f, "Ready"),
625 Self::Succeeded => write!(f, "Succeeded"),
626 Self::Failed => write!(f, "Failed"),
627 Self::Cancelled => write!(f, "Cancelled"),
628 Self::TimedOut => write!(f, "TimedOut"),
629 Self::Stopped => write!(f, "Stopped"),
630 }
631 }
632}
633
634#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
639#[repr(u8)]
640pub enum OperationAction {
641 #[serde(rename = "START")]
643 Start = 0,
644 #[serde(rename = "SUCCEED")]
646 Succeed = 1,
647 #[serde(rename = "FAIL")]
649 Fail = 2,
650 #[serde(rename = "CANCEL")]
653 Cancel = 3,
654 #[serde(rename = "RETRY")]
657 Retry = 4,
658}
659
660impl std::fmt::Display for OperationAction {
661 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
662 match self {
663 Self::Start => write!(f, "Start"),
664 Self::Succeed => write!(f, "Succeed"),
665 Self::Fail => write!(f, "Fail"),
666 Self::Cancel => write!(f, "Cancel"),
667 Self::Retry => write!(f, "Retry"),
668 }
669 }
670}
671
672#[derive(Debug, Clone, Serialize, Deserialize)]
674pub struct WaitOptions {
675 #[serde(rename = "WaitSeconds")]
677 pub wait_seconds: u64,
678}
679
680#[derive(Debug, Clone, Serialize, Deserialize)]
682pub struct StepOptions {
683 #[serde(
685 rename = "NextAttemptDelaySeconds",
686 skip_serializing_if = "Option::is_none"
687 )]
688 pub next_attempt_delay_seconds: Option<u64>,
689}
690
691#[derive(Debug, Clone, Serialize, Deserialize)]
693pub struct CallbackOptions {
694 #[serde(rename = "TimeoutSeconds", skip_serializing_if = "Option::is_none")]
696 pub timeout_seconds: Option<u64>,
697 #[serde(
699 rename = "HeartbeatTimeoutSeconds",
700 skip_serializing_if = "Option::is_none"
701 )]
702 pub heartbeat_timeout_seconds: Option<u64>,
703}
704
705#[derive(Debug, Clone, Serialize, Deserialize)]
707pub struct ChainedInvokeOptions {
708 #[serde(rename = "FunctionName")]
710 pub function_name: String,
711 #[serde(rename = "TenantId", skip_serializing_if = "Option::is_none")]
713 pub tenant_id: Option<String>,
714}
715
716#[derive(Debug, Clone, Serialize, Deserialize)]
718pub struct ContextOptions {
719 #[serde(rename = "ReplayChildren", skip_serializing_if = "Option::is_none")]
721 pub replay_children: Option<bool>,
722}
723
724#[derive(Debug, Clone, Serialize, Deserialize)]
729pub struct OperationUpdate {
730 #[serde(rename = "Id")]
732 pub operation_id: String,
733
734 #[serde(rename = "Action")]
736 pub action: OperationAction,
737
738 #[serde(rename = "Type")]
740 pub operation_type: OperationType,
741
742 #[serde(rename = "Payload", skip_serializing_if = "Option::is_none")]
744 pub result: Option<String>,
745
746 #[serde(rename = "Error", skip_serializing_if = "Option::is_none")]
748 pub error: Option<ErrorObject>,
749
750 #[serde(rename = "ParentId", skip_serializing_if = "Option::is_none")]
752 pub parent_id: Option<String>,
753
754 #[serde(rename = "Name", skip_serializing_if = "Option::is_none")]
756 pub name: Option<String>,
757
758 #[serde(rename = "SubType", skip_serializing_if = "Option::is_none")]
761 pub sub_type: Option<String>,
762
763 #[serde(rename = "WaitOptions", skip_serializing_if = "Option::is_none")]
765 pub wait_options: Option<WaitOptions>,
766
767 #[serde(rename = "StepOptions", skip_serializing_if = "Option::is_none")]
769 pub step_options: Option<StepOptions>,
770
771 #[serde(rename = "CallbackOptions", skip_serializing_if = "Option::is_none")]
773 pub callback_options: Option<CallbackOptions>,
774
775 #[serde(
777 rename = "ChainedInvokeOptions",
778 skip_serializing_if = "Option::is_none"
779 )]
780 pub chained_invoke_options: Option<ChainedInvokeOptions>,
781
782 #[serde(rename = "ContextOptions", skip_serializing_if = "Option::is_none")]
784 pub context_options: Option<ContextOptions>,
785}
786
787impl OperationUpdate {
788 pub fn start(operation_id: impl Into<String>, operation_type: OperationType) -> Self {
790 Self {
791 operation_id: operation_id.into(),
792 action: OperationAction::Start,
793 operation_type,
794 result: None,
795 error: None,
796 parent_id: None,
797 name: None,
798 sub_type: None,
799 wait_options: None,
800 step_options: None,
801 callback_options: None,
802 chained_invoke_options: None,
803 context_options: None,
804 }
805 }
806
807 pub fn start_wait(operation_id: impl Into<String>, wait_seconds: u64) -> Self {
809 Self {
810 operation_id: operation_id.into(),
811 action: OperationAction::Start,
812 operation_type: OperationType::Wait,
813 result: None,
814 error: None,
815 parent_id: None,
816 name: None,
817 sub_type: None,
818 wait_options: Some(WaitOptions { wait_seconds }),
819 step_options: None,
820 callback_options: None,
821 chained_invoke_options: None,
822 context_options: None,
823 }
824 }
825
826 pub fn succeed(
828 operation_id: impl Into<String>,
829 operation_type: OperationType,
830 result: Option<String>,
831 ) -> Self {
832 Self {
833 operation_id: operation_id.into(),
834 action: OperationAction::Succeed,
835 operation_type,
836 result,
837 error: None,
838 parent_id: None,
839 name: None,
840 sub_type: None,
841 wait_options: None,
842 step_options: None,
843 callback_options: None,
844 chained_invoke_options: None,
845 context_options: None,
846 }
847 }
848
849 pub fn fail(
851 operation_id: impl Into<String>,
852 operation_type: OperationType,
853 error: ErrorObject,
854 ) -> Self {
855 Self {
856 operation_id: operation_id.into(),
857 action: OperationAction::Fail,
858 operation_type,
859 result: None,
860 error: Some(error),
861 parent_id: None,
862 name: None,
863 sub_type: None,
864 wait_options: None,
865 step_options: None,
866 callback_options: None,
867 chained_invoke_options: None,
868 context_options: None,
869 }
870 }
871
872 pub fn cancel(operation_id: impl Into<String>, operation_type: OperationType) -> Self {
881 Self {
882 operation_id: operation_id.into(),
883 action: OperationAction::Cancel,
884 operation_type,
885 result: None,
886 error: None,
887 parent_id: None,
888 name: None,
889 sub_type: None,
890 wait_options: None,
891 step_options: None,
892 callback_options: None,
893 chained_invoke_options: None,
894 context_options: None,
895 }
896 }
897
898 pub fn retry(
911 operation_id: impl Into<String>,
912 operation_type: OperationType,
913 payload: Option<String>,
914 next_attempt_delay_seconds: Option<u64>,
915 ) -> Self {
916 Self {
917 operation_id: operation_id.into(),
918 action: OperationAction::Retry,
919 operation_type,
920 result: payload,
921 error: None,
922 parent_id: None,
923 name: None,
924 sub_type: None,
925 wait_options: None,
926 step_options: Some(StepOptions {
927 next_attempt_delay_seconds,
928 }),
929 callback_options: None,
930 chained_invoke_options: None,
931 context_options: None,
932 }
933 }
934
935 pub fn retry_with_error(
947 operation_id: impl Into<String>,
948 operation_type: OperationType,
949 error: ErrorObject,
950 next_attempt_delay_seconds: Option<u64>,
951 ) -> Self {
952 Self {
953 operation_id: operation_id.into(),
954 action: OperationAction::Retry,
955 operation_type,
956 result: None,
957 error: Some(error),
958 parent_id: None,
959 name: None,
960 sub_type: None,
961 wait_options: None,
962 step_options: Some(StepOptions {
963 next_attempt_delay_seconds,
964 }),
965 callback_options: None,
966 chained_invoke_options: None,
967 context_options: None,
968 }
969 }
970
971 pub fn with_parent_id(mut self, parent_id: impl Into<String>) -> Self {
973 self.parent_id = Some(parent_id.into());
974 self
975 }
976
977 pub fn with_name(mut self, name: impl Into<String>) -> Self {
979 self.name = Some(name.into());
980 self
981 }
982
983 pub fn with_sub_type(mut self, sub_type: impl Into<String>) -> Self {
986 self.sub_type = Some(sub_type.into());
987 self
988 }
989
990 pub fn with_wait_options(mut self, wait_seconds: u64) -> Self {
992 self.wait_options = Some(WaitOptions { wait_seconds });
993 self
994 }
995
996 pub fn with_step_options(mut self, next_attempt_delay_seconds: Option<u64>) -> Self {
998 self.step_options = Some(StepOptions {
999 next_attempt_delay_seconds,
1000 });
1001 self
1002 }
1003
1004 pub fn with_callback_options(
1006 mut self,
1007 timeout_seconds: Option<u64>,
1008 heartbeat_timeout_seconds: Option<u64>,
1009 ) -> Self {
1010 self.callback_options = Some(CallbackOptions {
1011 timeout_seconds,
1012 heartbeat_timeout_seconds,
1013 });
1014 self
1015 }
1016
1017 pub fn with_chained_invoke_options(
1019 mut self,
1020 function_name: impl Into<String>,
1021 tenant_id: Option<String>,
1022 ) -> Self {
1023 self.chained_invoke_options = Some(ChainedInvokeOptions {
1024 function_name: function_name.into(),
1025 tenant_id,
1026 });
1027 self
1028 }
1029
1030 pub fn with_context_options(mut self, replay_children: Option<bool>) -> Self {
1032 self.context_options = Some(ContextOptions { replay_children });
1033 self
1034 }
1035}
1036
1037#[cfg(test)]
1038mod tests {
1039 use super::*;
1040 use proptest::prelude::*;
1041
1042 fn operation_type_strategy() -> impl Strategy<Value = OperationType> {
1049 prop_oneof![
1050 Just(OperationType::Execution),
1051 Just(OperationType::Step),
1052 Just(OperationType::Wait),
1053 Just(OperationType::Callback),
1054 Just(OperationType::Invoke),
1055 Just(OperationType::Context),
1056 ]
1057 }
1058
1059 fn operation_status_strategy() -> impl Strategy<Value = OperationStatus> {
1062 prop_oneof![
1063 Just(OperationStatus::Started),
1064 Just(OperationStatus::Pending),
1065 Just(OperationStatus::Ready),
1066 Just(OperationStatus::Succeeded),
1067 Just(OperationStatus::Failed),
1068 Just(OperationStatus::Cancelled),
1069 Just(OperationStatus::TimedOut),
1070 Just(OperationStatus::Stopped),
1071 ]
1072 }
1073
1074 fn operation_action_strategy() -> impl Strategy<Value = OperationAction> {
1077 prop_oneof![
1078 Just(OperationAction::Start),
1079 Just(OperationAction::Succeed),
1080 Just(OperationAction::Fail),
1081 Just(OperationAction::Cancel),
1082 Just(OperationAction::Retry),
1083 ]
1084 }
1085
1086 fn non_empty_string_strategy() -> impl Strategy<Value = String> {
1088 "[a-zA-Z0-9_-]{1,64}".prop_map(|s| s)
1089 }
1090
1091 fn optional_string_strategy() -> impl Strategy<Value = Option<String>> {
1093 prop_oneof![Just(None), non_empty_string_strategy().prop_map(Some),]
1094 }
1095
1096 fn optional_result_strategy() -> impl Strategy<Value = Option<String>> {
1098 prop_oneof![
1099 Just(None),
1100 Just(Some(r#"{"value": 42}"#.to_string())),
1101 Just(Some(r#""simple string""#.to_string())),
1102 Just(Some("123".to_string())),
1103 Just(Some("true".to_string())),
1104 Just(Some("null".to_string())),
1105 ]
1106 }
1107
1108 fn optional_error_strategy() -> impl Strategy<Value = Option<ErrorObject>> {
1110 prop_oneof![
1111 Just(None),
1112 (non_empty_string_strategy(), non_empty_string_strategy())
1113 .prop_map(|(error_type, message)| Some(ErrorObject::new(error_type, message))),
1114 ]
1115 }
1116
1117 fn optional_timestamp_strategy() -> impl Strategy<Value = Option<i64>> {
1119 prop_oneof![
1120 Just(None),
1121 (1577836800000i64..1893456000000i64).prop_map(Some),
1123 ]
1124 }
1125
1126 fn operation_strategy() -> impl Strategy<Value = Operation> {
1129 (
1130 non_empty_string_strategy(), operation_type_strategy(), operation_status_strategy(), optional_result_strategy(), optional_error_strategy(), optional_string_strategy(), optional_string_strategy(), optional_string_strategy(), optional_timestamp_strategy(), optional_timestamp_strategy(), )
1141 .prop_map(
1142 |(
1143 operation_id,
1144 operation_type,
1145 status,
1146 result,
1147 error,
1148 parent_id,
1149 name,
1150 sub_type,
1151 start_timestamp,
1152 end_timestamp,
1153 )| {
1154 Operation {
1155 operation_id,
1156 operation_type,
1157 status,
1158 result,
1159 error,
1160 parent_id,
1161 name,
1162 sub_type,
1163 start_timestamp,
1164 end_timestamp,
1165 execution_details: None,
1166 step_details: None,
1167 wait_details: None,
1168 callback_details: None,
1169 chained_invoke_details: None,
1170 context_details: None,
1171 }
1172 },
1173 )
1174 }
1175
1176 proptest! {
1181 #[test]
1185 fn prop_operation_type_serialization_round_trip(op_type in operation_type_strategy()) {
1186 let json = serde_json::to_string(&op_type).expect("serialization should succeed");
1187 let deserialized: OperationType = serde_json::from_str(&json).expect("deserialization should succeed");
1188 prop_assert_eq!(op_type, deserialized, "Round-trip failed for {:?}", op_type);
1189 }
1190
1191 #[test]
1195 fn prop_operation_status_serialization_round_trip(status in operation_status_strategy()) {
1196 let json = serde_json::to_string(&status).expect("serialization should succeed");
1197 let deserialized: OperationStatus = serde_json::from_str(&json).expect("deserialization should succeed");
1198 prop_assert_eq!(status, deserialized, "Round-trip failed for {:?}", status);
1199 }
1200
1201 #[test]
1205 fn prop_operation_action_serialization_round_trip(action in operation_action_strategy()) {
1206 let json = serde_json::to_string(&action).expect("serialization should succeed");
1207 let deserialized: OperationAction = serde_json::from_str(&json).expect("deserialization should succeed");
1208 prop_assert_eq!(action, deserialized, "Round-trip failed for {:?}", action);
1209 }
1210
1211 #[test]
1217 fn prop_terminal_status_classification(status in operation_status_strategy()) {
1218 let is_terminal = status.is_terminal();
1219 let expected_terminal = matches!(
1220 status,
1221 OperationStatus::Succeeded
1222 | OperationStatus::Failed
1223 | OperationStatus::Cancelled
1224 | OperationStatus::TimedOut
1225 | OperationStatus::Stopped
1226 );
1227 prop_assert_eq!(
1228 is_terminal, expected_terminal,
1229 "Terminal classification mismatch for {:?}: got {}, expected {}",
1230 status, is_terminal, expected_terminal
1231 );
1232 }
1233
1234 #[test]
1239 fn prop_operation_serialization_round_trip(op in operation_strategy()) {
1240 let json = serde_json::to_string(&op).expect("serialization should succeed");
1241 let deserialized: Operation = serde_json::from_str(&json).expect("deserialization should succeed");
1242
1243 prop_assert_eq!(op.operation_id, deserialized.operation_id, "operation_id mismatch");
1245 prop_assert_eq!(op.operation_type, deserialized.operation_type, "operation_type mismatch");
1246 prop_assert_eq!(op.status, deserialized.status, "status mismatch");
1247 prop_assert_eq!(op.result, deserialized.result, "result mismatch");
1248 prop_assert_eq!(op.parent_id, deserialized.parent_id, "parent_id mismatch");
1249 prop_assert_eq!(op.name, deserialized.name, "name mismatch");
1250 prop_assert_eq!(op.sub_type, deserialized.sub_type, "sub_type mismatch");
1251 prop_assert_eq!(op.start_timestamp, deserialized.start_timestamp, "start_timestamp mismatch");
1252 prop_assert_eq!(op.end_timestamp, deserialized.end_timestamp, "end_timestamp mismatch");
1253
1254 match (&op.error, &deserialized.error) {
1256 (Some(e1), Some(e2)) => {
1257 prop_assert_eq!(&e1.error_type, &e2.error_type, "error_type mismatch");
1258 prop_assert_eq!(&e1.error_message, &e2.error_message, "error_message mismatch");
1259 }
1260 (None, None) => {}
1261 _ => prop_assert!(false, "error presence mismatch"),
1262 }
1263 }
1264 }
1265
1266 #[test]
1271 fn test_operation_new() {
1272 let op = Operation::new("op-123", OperationType::Step);
1273 assert_eq!(op.operation_id, "op-123");
1274 assert_eq!(op.operation_type, OperationType::Step);
1275 assert_eq!(op.status, OperationStatus::Started);
1276 assert!(op.result.is_none());
1277 assert!(op.error.is_none());
1278 assert!(op.parent_id.is_none());
1279 assert!(op.name.is_none());
1280 }
1281
1282 #[test]
1283 fn test_operation_with_parent_and_name() {
1284 let op = Operation::new("op-123", OperationType::Step)
1285 .with_parent_id("parent-456")
1286 .with_name("my-step");
1287 assert_eq!(op.parent_id, Some("parent-456".to_string()));
1288 assert_eq!(op.name, Some("my-step".to_string()));
1289 }
1290
1291 #[test]
1292 fn test_operation_is_completed() {
1293 let mut op = Operation::new("op-123", OperationType::Step);
1294 assert!(!op.is_completed());
1295
1296 op.status = OperationStatus::Succeeded;
1297 assert!(op.is_completed());
1298
1299 op.status = OperationStatus::Failed;
1300 assert!(op.is_completed());
1301
1302 op.status = OperationStatus::Cancelled;
1303 assert!(op.is_completed());
1304
1305 op.status = OperationStatus::TimedOut;
1306 assert!(op.is_completed());
1307
1308 op.status = OperationStatus::Stopped;
1309 assert!(op.is_completed());
1310 }
1311
1312 #[test]
1313 fn test_operation_is_succeeded() {
1314 let mut op = Operation::new("op-123", OperationType::Step);
1315 assert!(!op.is_succeeded());
1316
1317 op.status = OperationStatus::Succeeded;
1318 assert!(op.is_succeeded());
1319
1320 op.status = OperationStatus::Failed;
1321 assert!(!op.is_succeeded());
1322 }
1323
1324 #[test]
1325 fn test_operation_is_failed() {
1326 let mut op = Operation::new("op-123", OperationType::Step);
1327 assert!(!op.is_failed());
1328
1329 op.status = OperationStatus::Failed;
1330 assert!(op.is_failed());
1331
1332 op.status = OperationStatus::Cancelled;
1333 assert!(op.is_failed());
1334
1335 op.status = OperationStatus::TimedOut;
1336 assert!(op.is_failed());
1337
1338 op.status = OperationStatus::Succeeded;
1339 assert!(!op.is_failed());
1340 }
1341
1342 #[test]
1343 fn test_operation_type_display() {
1344 assert_eq!(OperationType::Execution.to_string(), "Execution");
1345 assert_eq!(OperationType::Step.to_string(), "Step");
1346 assert_eq!(OperationType::Wait.to_string(), "Wait");
1347 assert_eq!(OperationType::Callback.to_string(), "Callback");
1348 assert_eq!(OperationType::Invoke.to_string(), "Invoke");
1349 assert_eq!(OperationType::Context.to_string(), "Context");
1350 }
1351
1352 #[test]
1353 fn test_operation_status_is_terminal() {
1354 assert!(!OperationStatus::Started.is_terminal());
1355 assert!(!OperationStatus::Pending.is_terminal());
1356 assert!(!OperationStatus::Ready.is_terminal());
1357 assert!(OperationStatus::Succeeded.is_terminal());
1358 assert!(OperationStatus::Failed.is_terminal());
1359 assert!(OperationStatus::Cancelled.is_terminal());
1360 assert!(OperationStatus::TimedOut.is_terminal());
1361 assert!(OperationStatus::Stopped.is_terminal());
1362 }
1363
1364 #[test]
1365 fn test_operation_status_is_success() {
1366 assert!(!OperationStatus::Started.is_success());
1367 assert!(!OperationStatus::Pending.is_success());
1368 assert!(!OperationStatus::Ready.is_success());
1369 assert!(OperationStatus::Succeeded.is_success());
1370 assert!(!OperationStatus::Failed.is_success());
1371 }
1372
1373 #[test]
1374 fn test_operation_status_is_failure() {
1375 assert!(!OperationStatus::Started.is_failure());
1376 assert!(!OperationStatus::Pending.is_failure());
1377 assert!(!OperationStatus::Ready.is_failure());
1378 assert!(!OperationStatus::Succeeded.is_failure());
1379 assert!(OperationStatus::Failed.is_failure());
1380 assert!(OperationStatus::Cancelled.is_failure());
1381 assert!(OperationStatus::TimedOut.is_failure());
1382 assert!(OperationStatus::Stopped.is_failure());
1383 }
1384
1385 #[test]
1386 fn test_operation_status_is_pending() {
1387 assert!(!OperationStatus::Started.is_pending());
1388 assert!(OperationStatus::Pending.is_pending());
1389 assert!(!OperationStatus::Ready.is_pending());
1390 assert!(!OperationStatus::Succeeded.is_pending());
1391 assert!(!OperationStatus::Failed.is_pending());
1392 }
1393
1394 #[test]
1395 fn test_operation_status_is_ready() {
1396 assert!(!OperationStatus::Started.is_ready());
1397 assert!(!OperationStatus::Pending.is_ready());
1398 assert!(OperationStatus::Ready.is_ready());
1399 assert!(!OperationStatus::Succeeded.is_ready());
1400 assert!(!OperationStatus::Failed.is_ready());
1401 }
1402
1403 #[test]
1404 fn test_operation_status_is_resumable() {
1405 assert!(OperationStatus::Started.is_resumable());
1406 assert!(OperationStatus::Pending.is_resumable());
1407 assert!(OperationStatus::Ready.is_resumable());
1408 assert!(!OperationStatus::Succeeded.is_resumable());
1409 assert!(!OperationStatus::Failed.is_resumable());
1410 assert!(!OperationStatus::Cancelled.is_resumable());
1411 assert!(!OperationStatus::TimedOut.is_resumable());
1412 assert!(!OperationStatus::Stopped.is_resumable());
1413 }
1414
1415 #[test]
1416 fn test_operation_update_start() {
1417 let update = OperationUpdate::start("op-123", OperationType::Step);
1418 assert_eq!(update.operation_id, "op-123");
1419 assert_eq!(update.action, OperationAction::Start);
1420 assert_eq!(update.operation_type, OperationType::Step);
1421 assert!(update.result.is_none());
1422 assert!(update.error.is_none());
1423 }
1424
1425 #[test]
1426 fn test_operation_update_succeed() {
1427 let update = OperationUpdate::succeed(
1428 "op-123",
1429 OperationType::Step,
1430 Some(r#"{"value": 42}"#.to_string()),
1431 );
1432 assert_eq!(update.operation_id, "op-123");
1433 assert_eq!(update.action, OperationAction::Succeed);
1434 assert_eq!(update.result, Some(r#"{"value": 42}"#.to_string()));
1435 assert!(update.error.is_none());
1436 }
1437
1438 #[test]
1439 fn test_operation_update_fail() {
1440 let error = ErrorObject::new("TestError", "Something went wrong");
1441 let update = OperationUpdate::fail("op-123", OperationType::Step, error);
1442 assert_eq!(update.operation_id, "op-123");
1443 assert_eq!(update.action, OperationAction::Fail);
1444 assert!(update.result.is_none());
1445 assert!(update.error.is_some());
1446 assert_eq!(update.error.as_ref().unwrap().error_type, "TestError");
1447 }
1448
1449 #[test]
1450 fn test_operation_update_with_parent_and_name() {
1451 let update = OperationUpdate::start("op-123", OperationType::Step)
1452 .with_parent_id("parent-456")
1453 .with_name("my-step");
1454 assert_eq!(update.parent_id, Some("parent-456".to_string()));
1455 assert_eq!(update.name, Some("my-step".to_string()));
1456 }
1457
1458 #[test]
1459 fn test_operation_serialization() {
1460 let op = Operation::new("op-123", OperationType::Step)
1461 .with_parent_id("parent-456")
1462 .with_name("my-step");
1463
1464 let json = serde_json::to_string(&op).unwrap();
1465 assert!(json.contains("\"Id\":\"op-123\""));
1466 assert!(json.contains("\"Type\":\"STEP\""));
1467 assert!(json.contains("\"Status\":\"STARTED\""));
1468 assert!(json.contains("\"ParentId\":\"parent-456\""));
1469 assert!(json.contains("\"Name\":\"my-step\""));
1470 }
1471
1472 #[test]
1473 fn test_operation_deserialization() {
1474 let json = r#"{
1476 "Id": "op-123",
1477 "Type": "STEP",
1478 "Status": "SUCCEEDED",
1479 "Result": "{\"value\": 42}",
1480 "ParentId": "parent-456",
1481 "Name": "my-step"
1482 }"#;
1483
1484 let op: Operation = serde_json::from_str(json).unwrap();
1485 assert_eq!(op.operation_id, "op-123");
1486 assert_eq!(op.operation_type, OperationType::Step);
1487 assert_eq!(op.status, OperationStatus::Succeeded);
1488 assert_eq!(op.result, Some(r#"{"value": 42}"#.to_string()));
1489 assert_eq!(op.parent_id, Some("parent-456".to_string()));
1490 assert_eq!(op.name, Some("my-step".to_string()));
1491 }
1492
1493 #[test]
1494 fn test_operation_deserialization_legacy_field_names() {
1495 let json = r#"{
1497 "OperationId": "op-123",
1498 "OperationType": "STEP",
1499 "Status": "SUCCEEDED",
1500 "Result": "{\"value\": 42}",
1501 "ParentId": "parent-456",
1502 "Name": "my-step"
1503 }"#;
1504
1505 let op: Operation = serde_json::from_str(json).unwrap();
1506 assert_eq!(op.operation_id, "op-123");
1507 assert_eq!(op.operation_type, OperationType::Step);
1508 assert_eq!(op.status, OperationStatus::Succeeded);
1509 }
1510
1511 #[test]
1512 fn test_operation_deserialization_with_timestamps() {
1513 let json = r#"{
1515 "Id": "778f03ea-ab5a-3e77-8d6d-9119253f8565",
1516 "Name": "21e26aa2-4866-4c09-958a-15a272f16c87",
1517 "Type": "EXECUTION",
1518 "StartTimestamp": 1767896523358,
1519 "Status": "STARTED",
1520 "ExecutionDetails": {
1521 "InputPayload": "{\"order_id\":\"order-122342134\"}"
1522 }
1523 }"#;
1524
1525 let op: Operation = serde_json::from_str(json).unwrap();
1526 assert_eq!(op.operation_id, "778f03ea-ab5a-3e77-8d6d-9119253f8565");
1527 assert_eq!(op.operation_type, OperationType::Execution);
1528 assert_eq!(op.status, OperationStatus::Started);
1529 assert_eq!(op.start_timestamp, Some(1767896523358));
1530 assert!(op.execution_details.is_some());
1531 let details = op.execution_details.unwrap();
1532 assert!(details.input_payload.is_some());
1533 }
1534
1535 #[test]
1536 fn test_operation_update_serialization() {
1537 let update = OperationUpdate::succeed(
1538 "op-123",
1539 OperationType::Step,
1540 Some(r#"{"value": 42}"#.to_string()),
1541 )
1542 .with_parent_id("parent-456");
1543
1544 let json = serde_json::to_string(&update).unwrap();
1545 assert!(json.contains("\"Id\":\"op-123\""));
1546 assert!(json.contains("\"Action\":\"SUCCEED\""));
1547 assert!(json.contains("\"Type\":\"STEP\""));
1548 assert!(json.contains("\"Payload\":\"{\\\"value\\\": 42}\""));
1549 assert!(json.contains("\"ParentId\":\"parent-456\""));
1550 }
1551
1552 #[test]
1553 fn test_operation_status_pending_serialization() {
1554 let json = r#"{
1556 "Id": "op-123",
1557 "Type": "STEP",
1558 "Status": "PENDING"
1559 }"#;
1560
1561 let op: Operation = serde_json::from_str(json).unwrap();
1562 assert_eq!(op.status, OperationStatus::Pending);
1563 assert!(op.status.is_pending());
1564 assert!(!op.status.is_terminal());
1565 assert!(op.status.is_resumable());
1566 }
1567
1568 #[test]
1569 fn test_operation_status_ready_serialization() {
1570 let json = r#"{
1572 "Id": "op-123",
1573 "Type": "STEP",
1574 "Status": "READY"
1575 }"#;
1576
1577 let op: Operation = serde_json::from_str(json).unwrap();
1578 assert_eq!(op.status, OperationStatus::Ready);
1579 assert!(op.status.is_ready());
1580 assert!(!op.status.is_terminal());
1581 assert!(op.status.is_resumable());
1582 }
1583
1584 #[test]
1585 fn test_operation_status_display() {
1586 assert_eq!(OperationStatus::Started.to_string(), "Started");
1587 assert_eq!(OperationStatus::Pending.to_string(), "Pending");
1588 assert_eq!(OperationStatus::Ready.to_string(), "Ready");
1589 assert_eq!(OperationStatus::Succeeded.to_string(), "Succeeded");
1590 assert_eq!(OperationStatus::Failed.to_string(), "Failed");
1591 assert_eq!(OperationStatus::Cancelled.to_string(), "Cancelled");
1592 assert_eq!(OperationStatus::TimedOut.to_string(), "TimedOut");
1593 assert_eq!(OperationStatus::Stopped.to_string(), "Stopped");
1594 }
1595
1596 #[test]
1597 fn test_operation_with_sub_type() {
1598 let op = Operation::new("op-123", OperationType::Context).with_sub_type("map");
1599 assert_eq!(op.sub_type, Some("map".to_string()));
1600 }
1601
1602 #[test]
1603 fn test_operation_update_with_sub_type() {
1604 let update =
1605 OperationUpdate::start("op-123", OperationType::Context).with_sub_type("parallel");
1606 assert_eq!(update.sub_type, Some("parallel".to_string()));
1607 }
1608
1609 #[test]
1610 fn test_operation_sub_type_serialization() {
1611 let op =
1612 Operation::new("op-123", OperationType::Context).with_sub_type("wait_for_condition");
1613
1614 let json = serde_json::to_string(&op).unwrap();
1615 assert!(json.contains("\"SubType\":\"wait_for_condition\""));
1616 }
1617
1618 #[test]
1619 fn test_operation_sub_type_deserialization() {
1620 let json = r#"{
1621 "Id": "op-123",
1622 "Type": "CONTEXT",
1623 "Status": "STARTED",
1624 "SubType": "map"
1625 }"#;
1626
1627 let op: Operation = serde_json::from_str(json).unwrap();
1628 assert_eq!(op.sub_type, Some("map".to_string()));
1629 }
1630
1631 #[test]
1632 fn test_operation_metadata_fields() {
1633 let json = r#"{
1635 "Id": "op-123",
1636 "Type": "STEP",
1637 "Status": "SUCCEEDED",
1638 "StartTimestamp": 1704067200000,
1639 "EndTimestamp": 1704067260000,
1640 "Name": "my-step",
1641 "SubType": "custom"
1642 }"#;
1643
1644 let op: Operation = serde_json::from_str(json).unwrap();
1645 assert_eq!(op.start_timestamp, Some(1704067200000));
1646 assert_eq!(op.end_timestamp, Some(1704067260000));
1647 assert_eq!(op.name, Some("my-step".to_string()));
1648 assert_eq!(op.sub_type, Some("custom".to_string()));
1649 }
1650
1651 #[test]
1652 fn test_operation_action_retry_display() {
1653 assert_eq!(OperationAction::Retry.to_string(), "Retry");
1654 }
1655
1656 #[test]
1657 fn test_operation_update_retry_with_payload() {
1658 let update = OperationUpdate::retry(
1659 "op-123",
1660 OperationType::Step,
1661 Some(r#"{"state": "waiting"}"#.to_string()),
1662 Some(5),
1663 );
1664 assert_eq!(update.operation_id, "op-123");
1665 assert_eq!(update.action, OperationAction::Retry);
1666 assert_eq!(update.operation_type, OperationType::Step);
1667 assert_eq!(update.result, Some(r#"{"state": "waiting"}"#.to_string()));
1668 assert!(update.error.is_none());
1669 assert!(update.step_options.is_some());
1670 assert_eq!(
1671 update
1672 .step_options
1673 .as_ref()
1674 .unwrap()
1675 .next_attempt_delay_seconds,
1676 Some(5)
1677 );
1678 }
1679
1680 #[test]
1681 fn test_operation_update_retry_with_error() {
1682 let error = ErrorObject::new("RetryableError", "Temporary failure");
1683 let update =
1684 OperationUpdate::retry_with_error("op-123", OperationType::Step, error, Some(10));
1685 assert_eq!(update.operation_id, "op-123");
1686 assert_eq!(update.action, OperationAction::Retry);
1687 assert!(update.result.is_none());
1688 assert!(update.error.is_some());
1689 assert_eq!(update.error.as_ref().unwrap().error_type, "RetryableError");
1690 assert_eq!(
1691 update
1692 .step_options
1693 .as_ref()
1694 .unwrap()
1695 .next_attempt_delay_seconds,
1696 Some(10)
1697 );
1698 }
1699
1700 #[test]
1701 fn test_operation_update_retry_serialization() {
1702 let update = OperationUpdate::retry(
1703 "op-123",
1704 OperationType::Step,
1705 Some(r#"{"counter": 5}"#.to_string()),
1706 Some(3),
1707 );
1708
1709 let json = serde_json::to_string(&update).unwrap();
1710 assert!(json.contains("\"Action\":\"RETRY\""));
1711 assert!(json.contains("\"Payload\":\"{\\\"counter\\\": 5}\""));
1712 assert!(json.contains("\"NextAttemptDelaySeconds\":3"));
1713 }
1714
1715 #[test]
1716 fn test_step_details_with_payload() {
1717 let json = r#"{
1718 "Id": "op-123",
1719 "Type": "STEP",
1720 "Status": "PENDING",
1721 "StepDetails": {
1722 "Attempt": 2,
1723 "Payload": "{\"state\": \"processing\"}"
1724 }
1725 }"#;
1726
1727 let op: Operation = serde_json::from_str(json).unwrap();
1728 assert_eq!(op.status, OperationStatus::Pending);
1729 assert!(op.step_details.is_some());
1730 let details = op.step_details.as_ref().unwrap();
1731 assert_eq!(details.attempt, Some(2));
1732 assert_eq!(
1733 details.payload,
1734 Some(r#"{"state": "processing"}"#.to_string())
1735 );
1736 }
1737
1738 #[test]
1739 fn test_operation_get_retry_payload() {
1740 let mut op = Operation::new("op-123", OperationType::Step);
1741 op.step_details = Some(StepDetails {
1742 result: None,
1743 attempt: Some(1),
1744 next_attempt_timestamp: None,
1745 error: None,
1746 payload: Some(r#"{"counter": 3}"#.to_string()),
1747 });
1748
1749 assert_eq!(op.get_retry_payload(), Some(r#"{"counter": 3}"#));
1750 }
1751
1752 #[test]
1753 fn test_operation_get_attempt() {
1754 let mut op = Operation::new("op-123", OperationType::Step);
1755 op.step_details = Some(StepDetails {
1756 result: None,
1757 attempt: Some(5),
1758 next_attempt_timestamp: None,
1759 error: None,
1760 payload: None,
1761 });
1762
1763 assert_eq!(op.get_attempt(), Some(5));
1764 }
1765
1766 #[test]
1767 fn test_operation_get_attempt_no_details() {
1768 let op = Operation::new("op-123", OperationType::Step);
1769 assert_eq!(op.get_attempt(), None);
1770 }
1771
1772 #[test]
1773 fn test_operation_get_retry_payload_wrong_type() {
1774 let op = Operation::new("op-123", OperationType::Wait);
1775 assert_eq!(op.get_retry_payload(), None);
1776 }
1777
1778 #[test]
1782 fn test_operation_status_size_is_one_byte() {
1783 assert_eq!(
1784 std::mem::size_of::<OperationStatus>(),
1785 1,
1786 "OperationStatus should be 1 byte with #[repr(u8)]"
1787 );
1788 }
1789
1790 #[test]
1791 fn test_operation_type_size_is_one_byte() {
1792 assert_eq!(
1793 std::mem::size_of::<OperationType>(),
1794 1,
1795 "OperationType should be 1 byte with #[repr(u8)]"
1796 );
1797 }
1798
1799 #[test]
1800 fn test_operation_action_size_is_one_byte() {
1801 assert_eq!(
1802 std::mem::size_of::<OperationAction>(),
1803 1,
1804 "OperationAction should be 1 byte with #[repr(u8)]"
1805 );
1806 }
1807
1808 #[test]
1812 fn test_operation_status_serde_uses_string_representation() {
1813 let status = OperationStatus::Started;
1815 let json = serde_json::to_string(&status).unwrap();
1816 assert_eq!(json, "\"STARTED\"");
1817
1818 let status = OperationStatus::Pending;
1819 let json = serde_json::to_string(&status).unwrap();
1820 assert_eq!(json, "\"PENDING\"");
1821
1822 let status = OperationStatus::Ready;
1823 let json = serde_json::to_string(&status).unwrap();
1824 assert_eq!(json, "\"READY\"");
1825
1826 let status = OperationStatus::Succeeded;
1827 let json = serde_json::to_string(&status).unwrap();
1828 assert_eq!(json, "\"SUCCEEDED\"");
1829
1830 let status = OperationStatus::Failed;
1831 let json = serde_json::to_string(&status).unwrap();
1832 assert_eq!(json, "\"FAILED\"");
1833
1834 let status = OperationStatus::Cancelled;
1835 let json = serde_json::to_string(&status).unwrap();
1836 assert_eq!(json, "\"CANCELLED\"");
1837
1838 let status = OperationStatus::TimedOut;
1839 let json = serde_json::to_string(&status).unwrap();
1840 assert_eq!(json, "\"TIMED_OUT\"");
1841
1842 let status = OperationStatus::Stopped;
1843 let json = serde_json::to_string(&status).unwrap();
1844 assert_eq!(json, "\"STOPPED\"");
1845 }
1846
1847 #[test]
1848 fn test_operation_status_serde_round_trip() {
1849 let statuses = [
1850 OperationStatus::Started,
1851 OperationStatus::Pending,
1852 OperationStatus::Ready,
1853 OperationStatus::Succeeded,
1854 OperationStatus::Failed,
1855 OperationStatus::Cancelled,
1856 OperationStatus::TimedOut,
1857 OperationStatus::Stopped,
1858 ];
1859
1860 for status in statuses {
1861 let json = serde_json::to_string(&status).unwrap();
1862 let deserialized: OperationStatus = serde_json::from_str(&json).unwrap();
1863 assert_eq!(status, deserialized, "Round-trip failed for {:?}", status);
1864 }
1865 }
1866
1867 #[test]
1868 fn test_operation_type_serde_uses_string_representation() {
1869 let op_type = OperationType::Execution;
1871 let json = serde_json::to_string(&op_type).unwrap();
1872 assert_eq!(json, "\"EXECUTION\"");
1873
1874 let op_type = OperationType::Step;
1875 let json = serde_json::to_string(&op_type).unwrap();
1876 assert_eq!(json, "\"STEP\"");
1877
1878 let op_type = OperationType::Wait;
1879 let json = serde_json::to_string(&op_type).unwrap();
1880 assert_eq!(json, "\"WAIT\"");
1881
1882 let op_type = OperationType::Callback;
1883 let json = serde_json::to_string(&op_type).unwrap();
1884 assert_eq!(json, "\"CALLBACK\"");
1885
1886 let op_type = OperationType::Invoke;
1887 let json = serde_json::to_string(&op_type).unwrap();
1888 assert_eq!(json, "\"INVOKE\"");
1889
1890 let op_type = OperationType::Context;
1891 let json = serde_json::to_string(&op_type).unwrap();
1892 assert_eq!(json, "\"CONTEXT\"");
1893 }
1894
1895 #[test]
1896 fn test_operation_type_serde_round_trip() {
1897 let types = [
1898 OperationType::Execution,
1899 OperationType::Step,
1900 OperationType::Wait,
1901 OperationType::Callback,
1902 OperationType::Invoke,
1903 OperationType::Context,
1904 ];
1905
1906 for op_type in types {
1907 let json = serde_json::to_string(&op_type).unwrap();
1908 let deserialized: OperationType = serde_json::from_str(&json).unwrap();
1909 assert_eq!(op_type, deserialized, "Round-trip failed for {:?}", op_type);
1910 }
1911 }
1912
1913 #[test]
1914 fn test_operation_action_serde_uses_string_representation() {
1915 let action = OperationAction::Start;
1917 let json = serde_json::to_string(&action).unwrap();
1918 assert_eq!(json, "\"START\"");
1919
1920 let action = OperationAction::Succeed;
1921 let json = serde_json::to_string(&action).unwrap();
1922 assert_eq!(json, "\"SUCCEED\"");
1923
1924 let action = OperationAction::Fail;
1925 let json = serde_json::to_string(&action).unwrap();
1926 assert_eq!(json, "\"FAIL\"");
1927
1928 let action = OperationAction::Cancel;
1929 let json = serde_json::to_string(&action).unwrap();
1930 assert_eq!(json, "\"CANCEL\"");
1931
1932 let action = OperationAction::Retry;
1933 let json = serde_json::to_string(&action).unwrap();
1934 assert_eq!(json, "\"RETRY\"");
1935 }
1936
1937 #[test]
1938 fn test_operation_action_serde_round_trip() {
1939 let actions = [
1940 OperationAction::Start,
1941 OperationAction::Succeed,
1942 OperationAction::Fail,
1943 OperationAction::Cancel,
1944 OperationAction::Retry,
1945 ];
1946
1947 for action in actions {
1948 let json = serde_json::to_string(&action).unwrap();
1949 let deserialized: OperationAction = serde_json::from_str(&json).unwrap();
1950 assert_eq!(action, deserialized, "Round-trip failed for {:?}", action);
1951 }
1952 }
1953
1954 #[test]
1957 fn test_parse_iso8601_rfc3339_format() {
1958 let result = parse_iso8601_to_millis("2026-01-13T04:10:18.841+00:00");
1960 assert!(result.is_ok(), "Failed to parse: {:?}", result);
1961 let millis = result.unwrap();
1962 assert!(millis > 0, "Timestamp should be positive, got {}", millis);
1966 assert!(
1968 millis > 1577836800000 && millis < 4102444800000,
1969 "Timestamp {} is outside reasonable range",
1970 millis
1971 );
1972 }
1973
1974 #[test]
1975 fn test_parse_iso8601_with_space_separator() {
1976 let result = parse_iso8601_to_millis("2026-01-13 04:10:18.841055+00:00");
1978 assert!(result.is_ok());
1979 }
1980
1981 #[test]
1982 fn test_parse_iso8601_without_timezone() {
1983 let result = parse_iso8601_to_millis("2026-01-13T04:10:18.841");
1985 assert!(result.is_ok());
1986 }
1987
1988 #[test]
1989 fn test_parse_iso8601_without_fractional_seconds() {
1990 let result = parse_iso8601_to_millis("2026-01-13T04:10:18+00:00");
1992 assert!(result.is_ok());
1993 }
1994
1995 #[test]
1996 fn test_parse_iso8601_invalid_format() {
1997 let result = parse_iso8601_to_millis("not-a-timestamp");
1999 assert!(result.is_err());
2000 }
2001
2002 #[test]
2003 fn test_timestamp_deserialization_integer() {
2004 let json = r#"{
2005 "Id": "op-123",
2006 "Type": "STEP",
2007 "Status": "STARTED",
2008 "StartTimestamp": 1768279818841
2009 }"#;
2010
2011 let op: Operation = serde_json::from_str(json).unwrap();
2012 assert_eq!(op.start_timestamp, Some(1768279818841));
2013 }
2014
2015 #[test]
2016 fn test_timestamp_deserialization_float() {
2017 let json = r#"{
2019 "Id": "op-123",
2020 "Type": "STEP",
2021 "Status": "STARTED",
2022 "StartTimestamp": 1768279818.841
2023 }"#;
2024
2025 let op: Operation = serde_json::from_str(json).unwrap();
2026 assert_eq!(op.start_timestamp, Some(1768279818841));
2028 }
2029
2030 #[test]
2031 fn test_timestamp_deserialization_iso8601_string() {
2032 let json = r#"{
2033 "Id": "op-123",
2034 "Type": "STEP",
2035 "Status": "STARTED",
2036 "StartTimestamp": "2026-01-13T04:10:18.841+00:00"
2037 }"#;
2038
2039 let op: Operation = serde_json::from_str(json).unwrap();
2040 assert!(op.start_timestamp.is_some());
2041 let ts = op.start_timestamp.unwrap();
2042 assert!(
2044 ts > 1577836800000 && ts < 4102444800000,
2045 "Timestamp {} is outside reasonable range",
2046 ts
2047 );
2048 }
2049
2050 #[test]
2051 fn test_timestamp_deserialization_null() {
2052 let json = r#"{
2053 "Id": "op-123",
2054 "Type": "STEP",
2055 "Status": "STARTED",
2056 "StartTimestamp": null
2057 }"#;
2058
2059 let op: Operation = serde_json::from_str(json).unwrap();
2060 assert!(op.start_timestamp.is_none());
2061 }
2062
2063 #[test]
2064 fn test_timestamp_deserialization_missing() {
2065 let json = r#"{
2066 "Id": "op-123",
2067 "Type": "STEP",
2068 "Status": "STARTED"
2069 }"#;
2070
2071 let op: Operation = serde_json::from_str(json).unwrap();
2072 assert!(op.start_timestamp.is_none());
2073 }
2074}