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> {
448 if self.operation_type == OperationType::Step {
449 if let Some(ref details) = self.step_details {
450 return details.payload.as_deref();
451 }
452 }
453 None
454 }
455
456 pub fn get_attempt(&self) -> Option<u32> {
466 if self.operation_type == OperationType::Step {
467 if let Some(ref details) = self.step_details {
468 return details.attempt;
469 }
470 }
471 None
472 }
473}
474
475#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
497#[repr(u8)]
498pub enum OperationType {
499 #[serde(rename = "EXECUTION")]
501 Execution = 0,
502 #[serde(rename = "STEP")]
504 Step = 1,
505 #[serde(rename = "WAIT")]
507 Wait = 2,
508 #[serde(rename = "CALLBACK")]
510 Callback = 3,
511 #[serde(rename = "INVOKE")]
513 Invoke = 4,
514 #[serde(rename = "CONTEXT")]
516 Context = 5,
517}
518
519impl std::fmt::Display for OperationType {
520 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521 match self {
522 Self::Execution => write!(f, "Execution"),
523 Self::Step => write!(f, "Step"),
524 Self::Wait => write!(f, "Wait"),
525 Self::Callback => write!(f, "Callback"),
526 Self::Invoke => write!(f, "Invoke"),
527 Self::Context => write!(f, "Context"),
528 }
529 }
530}
531
532#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
558#[repr(u8)]
559pub enum OperationStatus {
560 #[serde(rename = "STARTED")]
562 Started = 0,
563 #[serde(rename = "PENDING")]
566 Pending = 1,
567 #[serde(rename = "READY")]
570 Ready = 2,
571 #[serde(rename = "SUCCEEDED")]
573 Succeeded = 3,
574 #[serde(rename = "FAILED")]
576 Failed = 4,
577 #[serde(rename = "CANCELLED")]
579 Cancelled = 5,
580 #[serde(rename = "TIMED_OUT")]
582 TimedOut = 6,
583 #[serde(rename = "STOPPED")]
585 Stopped = 7,
586}
587
588impl OperationStatus {
589 pub fn is_terminal(&self) -> bool {
591 !matches!(self, Self::Started | Self::Pending | Self::Ready)
592 }
593
594 pub fn is_success(&self) -> bool {
596 matches!(self, Self::Succeeded)
597 }
598
599 pub fn is_failure(&self) -> bool {
601 matches!(
602 self,
603 Self::Failed | Self::Cancelled | Self::TimedOut | Self::Stopped
604 )
605 }
606
607 pub fn is_pending(&self) -> bool {
610 matches!(self, Self::Pending)
611 }
612
613 pub fn is_ready(&self) -> bool {
616 matches!(self, Self::Ready)
617 }
618
619 pub fn is_resumable(&self) -> bool {
623 matches!(self, Self::Started | Self::Pending | Self::Ready)
624 }
625}
626
627impl std::fmt::Display for OperationStatus {
628 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
629 match self {
630 Self::Started => write!(f, "Started"),
631 Self::Pending => write!(f, "Pending"),
632 Self::Ready => write!(f, "Ready"),
633 Self::Succeeded => write!(f, "Succeeded"),
634 Self::Failed => write!(f, "Failed"),
635 Self::Cancelled => write!(f, "Cancelled"),
636 Self::TimedOut => write!(f, "TimedOut"),
637 Self::Stopped => write!(f, "Stopped"),
638 }
639 }
640}
641
642#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
647#[repr(u8)]
648pub enum OperationAction {
649 #[serde(rename = "START")]
651 Start = 0,
652 #[serde(rename = "SUCCEED")]
654 Succeed = 1,
655 #[serde(rename = "FAIL")]
657 Fail = 2,
658 #[serde(rename = "CANCEL")]
661 Cancel = 3,
662 #[serde(rename = "RETRY")]
665 Retry = 4,
666}
667
668impl std::fmt::Display for OperationAction {
669 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
670 match self {
671 Self::Start => write!(f, "Start"),
672 Self::Succeed => write!(f, "Succeed"),
673 Self::Fail => write!(f, "Fail"),
674 Self::Cancel => write!(f, "Cancel"),
675 Self::Retry => write!(f, "Retry"),
676 }
677 }
678}
679
680#[derive(Debug, Clone, Serialize, Deserialize)]
682pub struct WaitOptions {
683 #[serde(rename = "WaitSeconds")]
685 pub wait_seconds: u64,
686}
687
688#[derive(Debug, Clone, Serialize, Deserialize)]
690pub struct StepOptions {
691 #[serde(
693 rename = "NextAttemptDelaySeconds",
694 skip_serializing_if = "Option::is_none"
695 )]
696 pub next_attempt_delay_seconds: Option<u64>,
697}
698
699#[derive(Debug, Clone, Serialize, Deserialize)]
701pub struct CallbackOptions {
702 #[serde(rename = "TimeoutSeconds", skip_serializing_if = "Option::is_none")]
704 pub timeout_seconds: Option<u64>,
705 #[serde(
707 rename = "HeartbeatTimeoutSeconds",
708 skip_serializing_if = "Option::is_none"
709 )]
710 pub heartbeat_timeout_seconds: Option<u64>,
711}
712
713#[derive(Debug, Clone, Serialize, Deserialize)]
715pub struct ChainedInvokeOptions {
716 #[serde(rename = "FunctionName")]
718 pub function_name: String,
719 #[serde(rename = "TenantId", skip_serializing_if = "Option::is_none")]
721 pub tenant_id: Option<String>,
722}
723
724#[derive(Debug, Clone, Serialize, Deserialize)]
726pub struct ContextOptions {
727 #[serde(rename = "ReplayChildren", skip_serializing_if = "Option::is_none")]
729 pub replay_children: Option<bool>,
730}
731
732#[derive(Debug, Clone, Serialize, Deserialize)]
737pub struct OperationUpdate {
738 #[serde(rename = "Id")]
740 pub operation_id: String,
741
742 #[serde(rename = "Action")]
744 pub action: OperationAction,
745
746 #[serde(rename = "Type")]
748 pub operation_type: OperationType,
749
750 #[serde(rename = "Payload", skip_serializing_if = "Option::is_none")]
752 pub result: Option<String>,
753
754 #[serde(rename = "Error", skip_serializing_if = "Option::is_none")]
756 pub error: Option<ErrorObject>,
757
758 #[serde(rename = "ParentId", skip_serializing_if = "Option::is_none")]
760 pub parent_id: Option<String>,
761
762 #[serde(rename = "Name", skip_serializing_if = "Option::is_none")]
764 pub name: Option<String>,
765
766 #[serde(rename = "SubType", skip_serializing_if = "Option::is_none")]
769 pub sub_type: Option<String>,
770
771 #[serde(rename = "WaitOptions", skip_serializing_if = "Option::is_none")]
773 pub wait_options: Option<WaitOptions>,
774
775 #[serde(rename = "StepOptions", skip_serializing_if = "Option::is_none")]
777 pub step_options: Option<StepOptions>,
778
779 #[serde(rename = "CallbackOptions", skip_serializing_if = "Option::is_none")]
781 pub callback_options: Option<CallbackOptions>,
782
783 #[serde(
785 rename = "ChainedInvokeOptions",
786 skip_serializing_if = "Option::is_none"
787 )]
788 pub chained_invoke_options: Option<ChainedInvokeOptions>,
789
790 #[serde(rename = "ContextOptions", skip_serializing_if = "Option::is_none")]
792 pub context_options: Option<ContextOptions>,
793}
794
795impl OperationUpdate {
796 pub fn start(operation_id: impl Into<String>, operation_type: OperationType) -> Self {
798 Self {
799 operation_id: operation_id.into(),
800 action: OperationAction::Start,
801 operation_type,
802 result: None,
803 error: None,
804 parent_id: None,
805 name: None,
806 sub_type: None,
807 wait_options: None,
808 step_options: None,
809 callback_options: None,
810 chained_invoke_options: None,
811 context_options: None,
812 }
813 }
814
815 pub fn start_wait(operation_id: impl Into<String>, wait_seconds: u64) -> Self {
817 Self {
818 operation_id: operation_id.into(),
819 action: OperationAction::Start,
820 operation_type: OperationType::Wait,
821 result: None,
822 error: None,
823 parent_id: None,
824 name: None,
825 sub_type: None,
826 wait_options: Some(WaitOptions { wait_seconds }),
827 step_options: None,
828 callback_options: None,
829 chained_invoke_options: None,
830 context_options: None,
831 }
832 }
833
834 pub fn succeed(
836 operation_id: impl Into<String>,
837 operation_type: OperationType,
838 result: Option<String>,
839 ) -> Self {
840 Self {
841 operation_id: operation_id.into(),
842 action: OperationAction::Succeed,
843 operation_type,
844 result,
845 error: None,
846 parent_id: None,
847 name: None,
848 sub_type: None,
849 wait_options: None,
850 step_options: None,
851 callback_options: None,
852 chained_invoke_options: None,
853 context_options: None,
854 }
855 }
856
857 pub fn fail(
859 operation_id: impl Into<String>,
860 operation_type: OperationType,
861 error: ErrorObject,
862 ) -> Self {
863 Self {
864 operation_id: operation_id.into(),
865 action: OperationAction::Fail,
866 operation_type,
867 result: None,
868 error: Some(error),
869 parent_id: None,
870 name: None,
871 sub_type: None,
872 wait_options: None,
873 step_options: None,
874 callback_options: None,
875 chained_invoke_options: None,
876 context_options: None,
877 }
878 }
879
880 pub fn cancel(operation_id: impl Into<String>, operation_type: OperationType) -> Self {
893 Self {
894 operation_id: operation_id.into(),
895 action: OperationAction::Cancel,
896 operation_type,
897 result: None,
898 error: None,
899 parent_id: None,
900 name: None,
901 sub_type: None,
902 wait_options: None,
903 step_options: None,
904 callback_options: None,
905 chained_invoke_options: None,
906 context_options: None,
907 }
908 }
909
910 pub fn retry(
929 operation_id: impl Into<String>,
930 operation_type: OperationType,
931 payload: Option<String>,
932 next_attempt_delay_seconds: Option<u64>,
933 ) -> Self {
934 Self {
935 operation_id: operation_id.into(),
936 action: OperationAction::Retry,
937 operation_type,
938 result: payload,
939 error: None,
940 parent_id: None,
941 name: None,
942 sub_type: None,
943 wait_options: None,
944 step_options: Some(StepOptions {
945 next_attempt_delay_seconds,
946 }),
947 callback_options: None,
948 chained_invoke_options: None,
949 context_options: None,
950 }
951 }
952
953 pub fn retry_with_error(
969 operation_id: impl Into<String>,
970 operation_type: OperationType,
971 error: ErrorObject,
972 next_attempt_delay_seconds: Option<u64>,
973 ) -> Self {
974 Self {
975 operation_id: operation_id.into(),
976 action: OperationAction::Retry,
977 operation_type,
978 result: None,
979 error: Some(error),
980 parent_id: None,
981 name: None,
982 sub_type: None,
983 wait_options: None,
984 step_options: Some(StepOptions {
985 next_attempt_delay_seconds,
986 }),
987 callback_options: None,
988 chained_invoke_options: None,
989 context_options: None,
990 }
991 }
992
993 pub fn with_parent_id(mut self, parent_id: impl Into<String>) -> Self {
995 self.parent_id = Some(parent_id.into());
996 self
997 }
998
999 pub fn with_name(mut self, name: impl Into<String>) -> Self {
1001 self.name = Some(name.into());
1002 self
1003 }
1004
1005 pub fn with_sub_type(mut self, sub_type: impl Into<String>) -> Self {
1008 self.sub_type = Some(sub_type.into());
1009 self
1010 }
1011
1012 pub fn with_wait_options(mut self, wait_seconds: u64) -> Self {
1014 self.wait_options = Some(WaitOptions { wait_seconds });
1015 self
1016 }
1017
1018 pub fn with_step_options(mut self, next_attempt_delay_seconds: Option<u64>) -> Self {
1020 self.step_options = Some(StepOptions {
1021 next_attempt_delay_seconds,
1022 });
1023 self
1024 }
1025
1026 pub fn with_callback_options(
1028 mut self,
1029 timeout_seconds: Option<u64>,
1030 heartbeat_timeout_seconds: Option<u64>,
1031 ) -> Self {
1032 self.callback_options = Some(CallbackOptions {
1033 timeout_seconds,
1034 heartbeat_timeout_seconds,
1035 });
1036 self
1037 }
1038
1039 pub fn with_chained_invoke_options(
1041 mut self,
1042 function_name: impl Into<String>,
1043 tenant_id: Option<String>,
1044 ) -> Self {
1045 self.chained_invoke_options = Some(ChainedInvokeOptions {
1046 function_name: function_name.into(),
1047 tenant_id,
1048 });
1049 self
1050 }
1051
1052 pub fn with_context_options(mut self, replay_children: Option<bool>) -> Self {
1054 self.context_options = Some(ContextOptions { replay_children });
1055 self
1056 }
1057}
1058
1059#[cfg(test)]
1060mod tests {
1061 use super::*;
1062 use proptest::prelude::*;
1063
1064 fn operation_type_strategy() -> impl Strategy<Value = OperationType> {
1071 prop_oneof![
1072 Just(OperationType::Execution),
1073 Just(OperationType::Step),
1074 Just(OperationType::Wait),
1075 Just(OperationType::Callback),
1076 Just(OperationType::Invoke),
1077 Just(OperationType::Context),
1078 ]
1079 }
1080
1081 fn operation_status_strategy() -> impl Strategy<Value = OperationStatus> {
1084 prop_oneof![
1085 Just(OperationStatus::Started),
1086 Just(OperationStatus::Pending),
1087 Just(OperationStatus::Ready),
1088 Just(OperationStatus::Succeeded),
1089 Just(OperationStatus::Failed),
1090 Just(OperationStatus::Cancelled),
1091 Just(OperationStatus::TimedOut),
1092 Just(OperationStatus::Stopped),
1093 ]
1094 }
1095
1096 fn operation_action_strategy() -> impl Strategy<Value = OperationAction> {
1099 prop_oneof![
1100 Just(OperationAction::Start),
1101 Just(OperationAction::Succeed),
1102 Just(OperationAction::Fail),
1103 Just(OperationAction::Cancel),
1104 Just(OperationAction::Retry),
1105 ]
1106 }
1107
1108 fn non_empty_string_strategy() -> impl Strategy<Value = String> {
1110 "[a-zA-Z0-9_-]{1,64}".prop_map(|s| s)
1111 }
1112
1113 fn optional_string_strategy() -> impl Strategy<Value = Option<String>> {
1115 prop_oneof![Just(None), non_empty_string_strategy().prop_map(Some),]
1116 }
1117
1118 fn optional_result_strategy() -> impl Strategy<Value = Option<String>> {
1120 prop_oneof![
1121 Just(None),
1122 Just(Some(r#"{"value": 42}"#.to_string())),
1123 Just(Some(r#""simple string""#.to_string())),
1124 Just(Some("123".to_string())),
1125 Just(Some("true".to_string())),
1126 Just(Some("null".to_string())),
1127 ]
1128 }
1129
1130 fn optional_error_strategy() -> impl Strategy<Value = Option<ErrorObject>> {
1132 prop_oneof![
1133 Just(None),
1134 (non_empty_string_strategy(), non_empty_string_strategy())
1135 .prop_map(|(error_type, message)| Some(ErrorObject::new(error_type, message))),
1136 ]
1137 }
1138
1139 fn optional_timestamp_strategy() -> impl Strategy<Value = Option<i64>> {
1141 prop_oneof![
1142 Just(None),
1143 (1577836800000i64..1893456000000i64).prop_map(Some),
1145 ]
1146 }
1147
1148 fn operation_strategy() -> impl Strategy<Value = Operation> {
1151 (
1152 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(), )
1163 .prop_map(
1164 |(
1165 operation_id,
1166 operation_type,
1167 status,
1168 result,
1169 error,
1170 parent_id,
1171 name,
1172 sub_type,
1173 start_timestamp,
1174 end_timestamp,
1175 )| {
1176 Operation {
1177 operation_id,
1178 operation_type,
1179 status,
1180 result,
1181 error,
1182 parent_id,
1183 name,
1184 sub_type,
1185 start_timestamp,
1186 end_timestamp,
1187 execution_details: None,
1188 step_details: None,
1189 wait_details: None,
1190 callback_details: None,
1191 chained_invoke_details: None,
1192 context_details: None,
1193 }
1194 },
1195 )
1196 }
1197
1198 proptest! {
1203 #[test]
1207 fn prop_operation_type_serialization_round_trip(op_type in operation_type_strategy()) {
1208 let json = serde_json::to_string(&op_type).expect("serialization should succeed");
1209 let deserialized: OperationType = serde_json::from_str(&json).expect("deserialization should succeed");
1210 prop_assert_eq!(op_type, deserialized, "Round-trip failed for {:?}", op_type);
1211 }
1212
1213 #[test]
1217 fn prop_operation_status_serialization_round_trip(status in operation_status_strategy()) {
1218 let json = serde_json::to_string(&status).expect("serialization should succeed");
1219 let deserialized: OperationStatus = serde_json::from_str(&json).expect("deserialization should succeed");
1220 prop_assert_eq!(status, deserialized, "Round-trip failed for {:?}", status);
1221 }
1222
1223 #[test]
1227 fn prop_operation_action_serialization_round_trip(action in operation_action_strategy()) {
1228 let json = serde_json::to_string(&action).expect("serialization should succeed");
1229 let deserialized: OperationAction = serde_json::from_str(&json).expect("deserialization should succeed");
1230 prop_assert_eq!(action, deserialized, "Round-trip failed for {:?}", action);
1231 }
1232
1233 #[test]
1239 fn prop_terminal_status_classification(status in operation_status_strategy()) {
1240 let is_terminal = status.is_terminal();
1241 let expected_terminal = matches!(
1242 status,
1243 OperationStatus::Succeeded
1244 | OperationStatus::Failed
1245 | OperationStatus::Cancelled
1246 | OperationStatus::TimedOut
1247 | OperationStatus::Stopped
1248 );
1249 prop_assert_eq!(
1250 is_terminal, expected_terminal,
1251 "Terminal classification mismatch for {:?}: got {}, expected {}",
1252 status, is_terminal, expected_terminal
1253 );
1254 }
1255
1256 #[test]
1261 fn prop_operation_serialization_round_trip(op in operation_strategy()) {
1262 let json = serde_json::to_string(&op).expect("serialization should succeed");
1263 let deserialized: Operation = serde_json::from_str(&json).expect("deserialization should succeed");
1264
1265 prop_assert_eq!(op.operation_id, deserialized.operation_id, "operation_id mismatch");
1267 prop_assert_eq!(op.operation_type, deserialized.operation_type, "operation_type mismatch");
1268 prop_assert_eq!(op.status, deserialized.status, "status mismatch");
1269 prop_assert_eq!(op.result, deserialized.result, "result mismatch");
1270 prop_assert_eq!(op.parent_id, deserialized.parent_id, "parent_id mismatch");
1271 prop_assert_eq!(op.name, deserialized.name, "name mismatch");
1272 prop_assert_eq!(op.sub_type, deserialized.sub_type, "sub_type mismatch");
1273 prop_assert_eq!(op.start_timestamp, deserialized.start_timestamp, "start_timestamp mismatch");
1274 prop_assert_eq!(op.end_timestamp, deserialized.end_timestamp, "end_timestamp mismatch");
1275
1276 match (&op.error, &deserialized.error) {
1278 (Some(e1), Some(e2)) => {
1279 prop_assert_eq!(&e1.error_type, &e2.error_type, "error_type mismatch");
1280 prop_assert_eq!(&e1.error_message, &e2.error_message, "error_message mismatch");
1281 }
1282 (None, None) => {}
1283 _ => prop_assert!(false, "error presence mismatch"),
1284 }
1285 }
1286 }
1287
1288 #[test]
1293 fn test_operation_new() {
1294 let op = Operation::new("op-123", OperationType::Step);
1295 assert_eq!(op.operation_id, "op-123");
1296 assert_eq!(op.operation_type, OperationType::Step);
1297 assert_eq!(op.status, OperationStatus::Started);
1298 assert!(op.result.is_none());
1299 assert!(op.error.is_none());
1300 assert!(op.parent_id.is_none());
1301 assert!(op.name.is_none());
1302 }
1303
1304 #[test]
1305 fn test_operation_with_parent_and_name() {
1306 let op = Operation::new("op-123", OperationType::Step)
1307 .with_parent_id("parent-456")
1308 .with_name("my-step");
1309 assert_eq!(op.parent_id, Some("parent-456".to_string()));
1310 assert_eq!(op.name, Some("my-step".to_string()));
1311 }
1312
1313 #[test]
1314 fn test_operation_is_completed() {
1315 let mut op = Operation::new("op-123", OperationType::Step);
1316 assert!(!op.is_completed());
1317
1318 op.status = OperationStatus::Succeeded;
1319 assert!(op.is_completed());
1320
1321 op.status = OperationStatus::Failed;
1322 assert!(op.is_completed());
1323
1324 op.status = OperationStatus::Cancelled;
1325 assert!(op.is_completed());
1326
1327 op.status = OperationStatus::TimedOut;
1328 assert!(op.is_completed());
1329
1330 op.status = OperationStatus::Stopped;
1331 assert!(op.is_completed());
1332 }
1333
1334 #[test]
1335 fn test_operation_is_succeeded() {
1336 let mut op = Operation::new("op-123", OperationType::Step);
1337 assert!(!op.is_succeeded());
1338
1339 op.status = OperationStatus::Succeeded;
1340 assert!(op.is_succeeded());
1341
1342 op.status = OperationStatus::Failed;
1343 assert!(!op.is_succeeded());
1344 }
1345
1346 #[test]
1347 fn test_operation_is_failed() {
1348 let mut op = Operation::new("op-123", OperationType::Step);
1349 assert!(!op.is_failed());
1350
1351 op.status = OperationStatus::Failed;
1352 assert!(op.is_failed());
1353
1354 op.status = OperationStatus::Cancelled;
1355 assert!(op.is_failed());
1356
1357 op.status = OperationStatus::TimedOut;
1358 assert!(op.is_failed());
1359
1360 op.status = OperationStatus::Succeeded;
1361 assert!(!op.is_failed());
1362 }
1363
1364 #[test]
1365 fn test_operation_type_display() {
1366 assert_eq!(OperationType::Execution.to_string(), "Execution");
1367 assert_eq!(OperationType::Step.to_string(), "Step");
1368 assert_eq!(OperationType::Wait.to_string(), "Wait");
1369 assert_eq!(OperationType::Callback.to_string(), "Callback");
1370 assert_eq!(OperationType::Invoke.to_string(), "Invoke");
1371 assert_eq!(OperationType::Context.to_string(), "Context");
1372 }
1373
1374 #[test]
1375 fn test_operation_status_is_terminal() {
1376 assert!(!OperationStatus::Started.is_terminal());
1377 assert!(!OperationStatus::Pending.is_terminal());
1378 assert!(!OperationStatus::Ready.is_terminal());
1379 assert!(OperationStatus::Succeeded.is_terminal());
1380 assert!(OperationStatus::Failed.is_terminal());
1381 assert!(OperationStatus::Cancelled.is_terminal());
1382 assert!(OperationStatus::TimedOut.is_terminal());
1383 assert!(OperationStatus::Stopped.is_terminal());
1384 }
1385
1386 #[test]
1387 fn test_operation_status_is_success() {
1388 assert!(!OperationStatus::Started.is_success());
1389 assert!(!OperationStatus::Pending.is_success());
1390 assert!(!OperationStatus::Ready.is_success());
1391 assert!(OperationStatus::Succeeded.is_success());
1392 assert!(!OperationStatus::Failed.is_success());
1393 }
1394
1395 #[test]
1396 fn test_operation_status_is_failure() {
1397 assert!(!OperationStatus::Started.is_failure());
1398 assert!(!OperationStatus::Pending.is_failure());
1399 assert!(!OperationStatus::Ready.is_failure());
1400 assert!(!OperationStatus::Succeeded.is_failure());
1401 assert!(OperationStatus::Failed.is_failure());
1402 assert!(OperationStatus::Cancelled.is_failure());
1403 assert!(OperationStatus::TimedOut.is_failure());
1404 assert!(OperationStatus::Stopped.is_failure());
1405 }
1406
1407 #[test]
1408 fn test_operation_status_is_pending() {
1409 assert!(!OperationStatus::Started.is_pending());
1410 assert!(OperationStatus::Pending.is_pending());
1411 assert!(!OperationStatus::Ready.is_pending());
1412 assert!(!OperationStatus::Succeeded.is_pending());
1413 assert!(!OperationStatus::Failed.is_pending());
1414 }
1415
1416 #[test]
1417 fn test_operation_status_is_ready() {
1418 assert!(!OperationStatus::Started.is_ready());
1419 assert!(!OperationStatus::Pending.is_ready());
1420 assert!(OperationStatus::Ready.is_ready());
1421 assert!(!OperationStatus::Succeeded.is_ready());
1422 assert!(!OperationStatus::Failed.is_ready());
1423 }
1424
1425 #[test]
1426 fn test_operation_status_is_resumable() {
1427 assert!(OperationStatus::Started.is_resumable());
1428 assert!(OperationStatus::Pending.is_resumable());
1429 assert!(OperationStatus::Ready.is_resumable());
1430 assert!(!OperationStatus::Succeeded.is_resumable());
1431 assert!(!OperationStatus::Failed.is_resumable());
1432 assert!(!OperationStatus::Cancelled.is_resumable());
1433 assert!(!OperationStatus::TimedOut.is_resumable());
1434 assert!(!OperationStatus::Stopped.is_resumable());
1435 }
1436
1437 #[test]
1438 fn test_operation_update_start() {
1439 let update = OperationUpdate::start("op-123", OperationType::Step);
1440 assert_eq!(update.operation_id, "op-123");
1441 assert_eq!(update.action, OperationAction::Start);
1442 assert_eq!(update.operation_type, OperationType::Step);
1443 assert!(update.result.is_none());
1444 assert!(update.error.is_none());
1445 }
1446
1447 #[test]
1448 fn test_operation_update_succeed() {
1449 let update = OperationUpdate::succeed(
1450 "op-123",
1451 OperationType::Step,
1452 Some(r#"{"value": 42}"#.to_string()),
1453 );
1454 assert_eq!(update.operation_id, "op-123");
1455 assert_eq!(update.action, OperationAction::Succeed);
1456 assert_eq!(update.result, Some(r#"{"value": 42}"#.to_string()));
1457 assert!(update.error.is_none());
1458 }
1459
1460 #[test]
1461 fn test_operation_update_fail() {
1462 let error = ErrorObject::new("TestError", "Something went wrong");
1463 let update = OperationUpdate::fail("op-123", OperationType::Step, error);
1464 assert_eq!(update.operation_id, "op-123");
1465 assert_eq!(update.action, OperationAction::Fail);
1466 assert!(update.result.is_none());
1467 assert!(update.error.is_some());
1468 assert_eq!(update.error.as_ref().unwrap().error_type, "TestError");
1469 }
1470
1471 #[test]
1472 fn test_operation_update_with_parent_and_name() {
1473 let update = OperationUpdate::start("op-123", OperationType::Step)
1474 .with_parent_id("parent-456")
1475 .with_name("my-step");
1476 assert_eq!(update.parent_id, Some("parent-456".to_string()));
1477 assert_eq!(update.name, Some("my-step".to_string()));
1478 }
1479
1480 #[test]
1481 fn test_operation_serialization() {
1482 let op = Operation::new("op-123", OperationType::Step)
1483 .with_parent_id("parent-456")
1484 .with_name("my-step");
1485
1486 let json = serde_json::to_string(&op).unwrap();
1487 assert!(json.contains("\"Id\":\"op-123\""));
1488 assert!(json.contains("\"Type\":\"STEP\""));
1489 assert!(json.contains("\"Status\":\"STARTED\""));
1490 assert!(json.contains("\"ParentId\":\"parent-456\""));
1491 assert!(json.contains("\"Name\":\"my-step\""));
1492 }
1493
1494 #[test]
1495 fn test_operation_deserialization() {
1496 let json = r#"{
1498 "Id": "op-123",
1499 "Type": "STEP",
1500 "Status": "SUCCEEDED",
1501 "Result": "{\"value\": 42}",
1502 "ParentId": "parent-456",
1503 "Name": "my-step"
1504 }"#;
1505
1506 let op: Operation = serde_json::from_str(json).unwrap();
1507 assert_eq!(op.operation_id, "op-123");
1508 assert_eq!(op.operation_type, OperationType::Step);
1509 assert_eq!(op.status, OperationStatus::Succeeded);
1510 assert_eq!(op.result, Some(r#"{"value": 42}"#.to_string()));
1511 assert_eq!(op.parent_id, Some("parent-456".to_string()));
1512 assert_eq!(op.name, Some("my-step".to_string()));
1513 }
1514
1515 #[test]
1516 fn test_operation_deserialization_legacy_field_names() {
1517 let json = r#"{
1519 "OperationId": "op-123",
1520 "OperationType": "STEP",
1521 "Status": "SUCCEEDED",
1522 "Result": "{\"value\": 42}",
1523 "ParentId": "parent-456",
1524 "Name": "my-step"
1525 }"#;
1526
1527 let op: Operation = serde_json::from_str(json).unwrap();
1528 assert_eq!(op.operation_id, "op-123");
1529 assert_eq!(op.operation_type, OperationType::Step);
1530 assert_eq!(op.status, OperationStatus::Succeeded);
1531 }
1532
1533 #[test]
1534 fn test_operation_deserialization_with_timestamps() {
1535 let json = r#"{
1537 "Id": "778f03ea-ab5a-3e77-8d6d-9119253f8565",
1538 "Name": "21e26aa2-4866-4c09-958a-15a272f16c87",
1539 "Type": "EXECUTION",
1540 "StartTimestamp": 1767896523358,
1541 "Status": "STARTED",
1542 "ExecutionDetails": {
1543 "InputPayload": "{\"order_id\":\"order-122342134\"}"
1544 }
1545 }"#;
1546
1547 let op: Operation = serde_json::from_str(json).unwrap();
1548 assert_eq!(op.operation_id, "778f03ea-ab5a-3e77-8d6d-9119253f8565");
1549 assert_eq!(op.operation_type, OperationType::Execution);
1550 assert_eq!(op.status, OperationStatus::Started);
1551 assert_eq!(op.start_timestamp, Some(1767896523358));
1552 assert!(op.execution_details.is_some());
1553 let details = op.execution_details.unwrap();
1554 assert!(details.input_payload.is_some());
1555 }
1556
1557 #[test]
1558 fn test_operation_update_serialization() {
1559 let update = OperationUpdate::succeed(
1560 "op-123",
1561 OperationType::Step,
1562 Some(r#"{"value": 42}"#.to_string()),
1563 )
1564 .with_parent_id("parent-456");
1565
1566 let json = serde_json::to_string(&update).unwrap();
1567 assert!(json.contains("\"Id\":\"op-123\""));
1568 assert!(json.contains("\"Action\":\"SUCCEED\""));
1569 assert!(json.contains("\"Type\":\"STEP\""));
1570 assert!(json.contains("\"Payload\":\"{\\\"value\\\": 42}\""));
1571 assert!(json.contains("\"ParentId\":\"parent-456\""));
1572 }
1573
1574 #[test]
1575 fn test_operation_status_pending_serialization() {
1576 let json = r#"{
1578 "Id": "op-123",
1579 "Type": "STEP",
1580 "Status": "PENDING"
1581 }"#;
1582
1583 let op: Operation = serde_json::from_str(json).unwrap();
1584 assert_eq!(op.status, OperationStatus::Pending);
1585 assert!(op.status.is_pending());
1586 assert!(!op.status.is_terminal());
1587 assert!(op.status.is_resumable());
1588 }
1589
1590 #[test]
1591 fn test_operation_status_ready_serialization() {
1592 let json = r#"{
1594 "Id": "op-123",
1595 "Type": "STEP",
1596 "Status": "READY"
1597 }"#;
1598
1599 let op: Operation = serde_json::from_str(json).unwrap();
1600 assert_eq!(op.status, OperationStatus::Ready);
1601 assert!(op.status.is_ready());
1602 assert!(!op.status.is_terminal());
1603 assert!(op.status.is_resumable());
1604 }
1605
1606 #[test]
1607 fn test_operation_status_display() {
1608 assert_eq!(OperationStatus::Started.to_string(), "Started");
1609 assert_eq!(OperationStatus::Pending.to_string(), "Pending");
1610 assert_eq!(OperationStatus::Ready.to_string(), "Ready");
1611 assert_eq!(OperationStatus::Succeeded.to_string(), "Succeeded");
1612 assert_eq!(OperationStatus::Failed.to_string(), "Failed");
1613 assert_eq!(OperationStatus::Cancelled.to_string(), "Cancelled");
1614 assert_eq!(OperationStatus::TimedOut.to_string(), "TimedOut");
1615 assert_eq!(OperationStatus::Stopped.to_string(), "Stopped");
1616 }
1617
1618 #[test]
1619 fn test_operation_with_sub_type() {
1620 let op = Operation::new("op-123", OperationType::Context).with_sub_type("map");
1621 assert_eq!(op.sub_type, Some("map".to_string()));
1622 }
1623
1624 #[test]
1625 fn test_operation_update_with_sub_type() {
1626 let update =
1627 OperationUpdate::start("op-123", OperationType::Context).with_sub_type("parallel");
1628 assert_eq!(update.sub_type, Some("parallel".to_string()));
1629 }
1630
1631 #[test]
1632 fn test_operation_sub_type_serialization() {
1633 let op =
1634 Operation::new("op-123", OperationType::Context).with_sub_type("wait_for_condition");
1635
1636 let json = serde_json::to_string(&op).unwrap();
1637 assert!(json.contains("\"SubType\":\"wait_for_condition\""));
1638 }
1639
1640 #[test]
1641 fn test_operation_sub_type_deserialization() {
1642 let json = r#"{
1643 "Id": "op-123",
1644 "Type": "CONTEXT",
1645 "Status": "STARTED",
1646 "SubType": "map"
1647 }"#;
1648
1649 let op: Operation = serde_json::from_str(json).unwrap();
1650 assert_eq!(op.sub_type, Some("map".to_string()));
1651 }
1652
1653 #[test]
1654 fn test_operation_metadata_fields() {
1655 let json = r#"{
1657 "Id": "op-123",
1658 "Type": "STEP",
1659 "Status": "SUCCEEDED",
1660 "StartTimestamp": 1704067200000,
1661 "EndTimestamp": 1704067260000,
1662 "Name": "my-step",
1663 "SubType": "custom"
1664 }"#;
1665
1666 let op: Operation = serde_json::from_str(json).unwrap();
1667 assert_eq!(op.start_timestamp, Some(1704067200000));
1668 assert_eq!(op.end_timestamp, Some(1704067260000));
1669 assert_eq!(op.name, Some("my-step".to_string()));
1670 assert_eq!(op.sub_type, Some("custom".to_string()));
1671 }
1672
1673 #[test]
1674 fn test_operation_action_retry_display() {
1675 assert_eq!(OperationAction::Retry.to_string(), "Retry");
1676 }
1677
1678 #[test]
1679 fn test_operation_update_retry_with_payload() {
1680 let update = OperationUpdate::retry(
1681 "op-123",
1682 OperationType::Step,
1683 Some(r#"{"state": "waiting"}"#.to_string()),
1684 Some(5),
1685 );
1686 assert_eq!(update.operation_id, "op-123");
1687 assert_eq!(update.action, OperationAction::Retry);
1688 assert_eq!(update.operation_type, OperationType::Step);
1689 assert_eq!(update.result, Some(r#"{"state": "waiting"}"#.to_string()));
1690 assert!(update.error.is_none());
1691 assert!(update.step_options.is_some());
1692 assert_eq!(
1693 update
1694 .step_options
1695 .as_ref()
1696 .unwrap()
1697 .next_attempt_delay_seconds,
1698 Some(5)
1699 );
1700 }
1701
1702 #[test]
1703 fn test_operation_update_retry_with_error() {
1704 let error = ErrorObject::new("RetryableError", "Temporary failure");
1705 let update =
1706 OperationUpdate::retry_with_error("op-123", OperationType::Step, error, Some(10));
1707 assert_eq!(update.operation_id, "op-123");
1708 assert_eq!(update.action, OperationAction::Retry);
1709 assert!(update.result.is_none());
1710 assert!(update.error.is_some());
1711 assert_eq!(update.error.as_ref().unwrap().error_type, "RetryableError");
1712 assert_eq!(
1713 update
1714 .step_options
1715 .as_ref()
1716 .unwrap()
1717 .next_attempt_delay_seconds,
1718 Some(10)
1719 );
1720 }
1721
1722 #[test]
1723 fn test_operation_update_retry_serialization() {
1724 let update = OperationUpdate::retry(
1725 "op-123",
1726 OperationType::Step,
1727 Some(r#"{"counter": 5}"#.to_string()),
1728 Some(3),
1729 );
1730
1731 let json = serde_json::to_string(&update).unwrap();
1732 assert!(json.contains("\"Action\":\"RETRY\""));
1733 assert!(json.contains("\"Payload\":\"{\\\"counter\\\": 5}\""));
1734 assert!(json.contains("\"NextAttemptDelaySeconds\":3"));
1735 }
1736
1737 #[test]
1738 fn test_step_details_with_payload() {
1739 let json = r#"{
1740 "Id": "op-123",
1741 "Type": "STEP",
1742 "Status": "PENDING",
1743 "StepDetails": {
1744 "Attempt": 2,
1745 "Payload": "{\"state\": \"processing\"}"
1746 }
1747 }"#;
1748
1749 let op: Operation = serde_json::from_str(json).unwrap();
1750 assert_eq!(op.status, OperationStatus::Pending);
1751 assert!(op.step_details.is_some());
1752 let details = op.step_details.as_ref().unwrap();
1753 assert_eq!(details.attempt, Some(2));
1754 assert_eq!(
1755 details.payload,
1756 Some(r#"{"state": "processing"}"#.to_string())
1757 );
1758 }
1759
1760 #[test]
1761 fn test_operation_get_retry_payload() {
1762 let mut op = Operation::new("op-123", OperationType::Step);
1763 op.step_details = Some(StepDetails {
1764 result: None,
1765 attempt: Some(1),
1766 next_attempt_timestamp: None,
1767 error: None,
1768 payload: Some(r#"{"counter": 3}"#.to_string()),
1769 });
1770
1771 assert_eq!(op.get_retry_payload(), Some(r#"{"counter": 3}"#));
1772 }
1773
1774 #[test]
1775 fn test_operation_get_attempt() {
1776 let mut op = Operation::new("op-123", OperationType::Step);
1777 op.step_details = Some(StepDetails {
1778 result: None,
1779 attempt: Some(5),
1780 next_attempt_timestamp: None,
1781 error: None,
1782 payload: None,
1783 });
1784
1785 assert_eq!(op.get_attempt(), Some(5));
1786 }
1787
1788 #[test]
1789 fn test_operation_get_attempt_no_details() {
1790 let op = Operation::new("op-123", OperationType::Step);
1791 assert_eq!(op.get_attempt(), None);
1792 }
1793
1794 #[test]
1795 fn test_operation_get_retry_payload_wrong_type() {
1796 let op = Operation::new("op-123", OperationType::Wait);
1797 assert_eq!(op.get_retry_payload(), None);
1798 }
1799
1800 #[test]
1804 fn test_operation_status_size_is_one_byte() {
1805 assert_eq!(
1806 std::mem::size_of::<OperationStatus>(),
1807 1,
1808 "OperationStatus should be 1 byte with #[repr(u8)]"
1809 );
1810 }
1811
1812 #[test]
1813 fn test_operation_type_size_is_one_byte() {
1814 assert_eq!(
1815 std::mem::size_of::<OperationType>(),
1816 1,
1817 "OperationType should be 1 byte with #[repr(u8)]"
1818 );
1819 }
1820
1821 #[test]
1822 fn test_operation_action_size_is_one_byte() {
1823 assert_eq!(
1824 std::mem::size_of::<OperationAction>(),
1825 1,
1826 "OperationAction should be 1 byte with #[repr(u8)]"
1827 );
1828 }
1829
1830 #[test]
1834 fn test_operation_status_serde_uses_string_representation() {
1835 let status = OperationStatus::Started;
1837 let json = serde_json::to_string(&status).unwrap();
1838 assert_eq!(json, "\"STARTED\"");
1839
1840 let status = OperationStatus::Pending;
1841 let json = serde_json::to_string(&status).unwrap();
1842 assert_eq!(json, "\"PENDING\"");
1843
1844 let status = OperationStatus::Ready;
1845 let json = serde_json::to_string(&status).unwrap();
1846 assert_eq!(json, "\"READY\"");
1847
1848 let status = OperationStatus::Succeeded;
1849 let json = serde_json::to_string(&status).unwrap();
1850 assert_eq!(json, "\"SUCCEEDED\"");
1851
1852 let status = OperationStatus::Failed;
1853 let json = serde_json::to_string(&status).unwrap();
1854 assert_eq!(json, "\"FAILED\"");
1855
1856 let status = OperationStatus::Cancelled;
1857 let json = serde_json::to_string(&status).unwrap();
1858 assert_eq!(json, "\"CANCELLED\"");
1859
1860 let status = OperationStatus::TimedOut;
1861 let json = serde_json::to_string(&status).unwrap();
1862 assert_eq!(json, "\"TIMED_OUT\"");
1863
1864 let status = OperationStatus::Stopped;
1865 let json = serde_json::to_string(&status).unwrap();
1866 assert_eq!(json, "\"STOPPED\"");
1867 }
1868
1869 #[test]
1870 fn test_operation_status_serde_round_trip() {
1871 let statuses = [
1872 OperationStatus::Started,
1873 OperationStatus::Pending,
1874 OperationStatus::Ready,
1875 OperationStatus::Succeeded,
1876 OperationStatus::Failed,
1877 OperationStatus::Cancelled,
1878 OperationStatus::TimedOut,
1879 OperationStatus::Stopped,
1880 ];
1881
1882 for status in statuses {
1883 let json = serde_json::to_string(&status).unwrap();
1884 let deserialized: OperationStatus = serde_json::from_str(&json).unwrap();
1885 assert_eq!(status, deserialized, "Round-trip failed for {:?}", status);
1886 }
1887 }
1888
1889 #[test]
1890 fn test_operation_type_serde_uses_string_representation() {
1891 let op_type = OperationType::Execution;
1893 let json = serde_json::to_string(&op_type).unwrap();
1894 assert_eq!(json, "\"EXECUTION\"");
1895
1896 let op_type = OperationType::Step;
1897 let json = serde_json::to_string(&op_type).unwrap();
1898 assert_eq!(json, "\"STEP\"");
1899
1900 let op_type = OperationType::Wait;
1901 let json = serde_json::to_string(&op_type).unwrap();
1902 assert_eq!(json, "\"WAIT\"");
1903
1904 let op_type = OperationType::Callback;
1905 let json = serde_json::to_string(&op_type).unwrap();
1906 assert_eq!(json, "\"CALLBACK\"");
1907
1908 let op_type = OperationType::Invoke;
1909 let json = serde_json::to_string(&op_type).unwrap();
1910 assert_eq!(json, "\"INVOKE\"");
1911
1912 let op_type = OperationType::Context;
1913 let json = serde_json::to_string(&op_type).unwrap();
1914 assert_eq!(json, "\"CONTEXT\"");
1915 }
1916
1917 #[test]
1918 fn test_operation_type_serde_round_trip() {
1919 let types = [
1920 OperationType::Execution,
1921 OperationType::Step,
1922 OperationType::Wait,
1923 OperationType::Callback,
1924 OperationType::Invoke,
1925 OperationType::Context,
1926 ];
1927
1928 for op_type in types {
1929 let json = serde_json::to_string(&op_type).unwrap();
1930 let deserialized: OperationType = serde_json::from_str(&json).unwrap();
1931 assert_eq!(op_type, deserialized, "Round-trip failed for {:?}", op_type);
1932 }
1933 }
1934
1935 #[test]
1936 fn test_operation_action_serde_uses_string_representation() {
1937 let action = OperationAction::Start;
1939 let json = serde_json::to_string(&action).unwrap();
1940 assert_eq!(json, "\"START\"");
1941
1942 let action = OperationAction::Succeed;
1943 let json = serde_json::to_string(&action).unwrap();
1944 assert_eq!(json, "\"SUCCEED\"");
1945
1946 let action = OperationAction::Fail;
1947 let json = serde_json::to_string(&action).unwrap();
1948 assert_eq!(json, "\"FAIL\"");
1949
1950 let action = OperationAction::Cancel;
1951 let json = serde_json::to_string(&action).unwrap();
1952 assert_eq!(json, "\"CANCEL\"");
1953
1954 let action = OperationAction::Retry;
1955 let json = serde_json::to_string(&action).unwrap();
1956 assert_eq!(json, "\"RETRY\"");
1957 }
1958
1959 #[test]
1960 fn test_operation_action_serde_round_trip() {
1961 let actions = [
1962 OperationAction::Start,
1963 OperationAction::Succeed,
1964 OperationAction::Fail,
1965 OperationAction::Cancel,
1966 OperationAction::Retry,
1967 ];
1968
1969 for action in actions {
1970 let json = serde_json::to_string(&action).unwrap();
1971 let deserialized: OperationAction = serde_json::from_str(&json).unwrap();
1972 assert_eq!(action, deserialized, "Round-trip failed for {:?}", action);
1973 }
1974 }
1975
1976 #[test]
1979 fn test_parse_iso8601_rfc3339_format() {
1980 let result = parse_iso8601_to_millis("2026-01-13T04:10:18.841+00:00");
1982 assert!(result.is_ok(), "Failed to parse: {:?}", result);
1983 let millis = result.unwrap();
1984 assert!(millis > 0, "Timestamp should be positive, got {}", millis);
1988 assert!(
1990 millis > 1577836800000 && millis < 4102444800000,
1991 "Timestamp {} is outside reasonable range",
1992 millis
1993 );
1994 }
1995
1996 #[test]
1997 fn test_parse_iso8601_with_space_separator() {
1998 let result = parse_iso8601_to_millis("2026-01-13 04:10:18.841055+00:00");
2000 assert!(result.is_ok());
2001 }
2002
2003 #[test]
2004 fn test_parse_iso8601_without_timezone() {
2005 let result = parse_iso8601_to_millis("2026-01-13T04:10:18.841");
2007 assert!(result.is_ok());
2008 }
2009
2010 #[test]
2011 fn test_parse_iso8601_without_fractional_seconds() {
2012 let result = parse_iso8601_to_millis("2026-01-13T04:10:18+00:00");
2014 assert!(result.is_ok());
2015 }
2016
2017 #[test]
2018 fn test_parse_iso8601_invalid_format() {
2019 let result = parse_iso8601_to_millis("not-a-timestamp");
2021 assert!(result.is_err());
2022 }
2023
2024 #[test]
2025 fn test_timestamp_deserialization_integer() {
2026 let json = r#"{
2027 "Id": "op-123",
2028 "Type": "STEP",
2029 "Status": "STARTED",
2030 "StartTimestamp": 1768279818841
2031 }"#;
2032
2033 let op: Operation = serde_json::from_str(json).unwrap();
2034 assert_eq!(op.start_timestamp, Some(1768279818841));
2035 }
2036
2037 #[test]
2038 fn test_timestamp_deserialization_float() {
2039 let json = r#"{
2041 "Id": "op-123",
2042 "Type": "STEP",
2043 "Status": "STARTED",
2044 "StartTimestamp": 1768279818.841
2045 }"#;
2046
2047 let op: Operation = serde_json::from_str(json).unwrap();
2048 assert_eq!(op.start_timestamp, Some(1768279818841));
2050 }
2051
2052 #[test]
2053 fn test_timestamp_deserialization_iso8601_string() {
2054 let json = r#"{
2055 "Id": "op-123",
2056 "Type": "STEP",
2057 "Status": "STARTED",
2058 "StartTimestamp": "2026-01-13T04:10:18.841+00:00"
2059 }"#;
2060
2061 let op: Operation = serde_json::from_str(json).unwrap();
2062 assert!(op.start_timestamp.is_some());
2063 let ts = op.start_timestamp.unwrap();
2064 assert!(
2066 ts > 1577836800000 && ts < 4102444800000,
2067 "Timestamp {} is outside reasonable range",
2068 ts
2069 );
2070 }
2071
2072 #[test]
2073 fn test_timestamp_deserialization_null() {
2074 let json = r#"{
2075 "Id": "op-123",
2076 "Type": "STEP",
2077 "Status": "STARTED",
2078 "StartTimestamp": null
2079 }"#;
2080
2081 let op: Operation = serde_json::from_str(json).unwrap();
2082 assert!(op.start_timestamp.is_none());
2083 }
2084
2085 #[test]
2086 fn test_timestamp_deserialization_missing() {
2087 let json = r#"{
2088 "Id": "op-123",
2089 "Type": "STEP",
2090 "Status": "STARTED"
2091 }"#;
2092
2093 let op: Operation = serde_json::from_str(json).unwrap();
2094 assert!(op.start_timestamp.is_none());
2095 }
2096}