1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::error::{FlowError, Result};
5
6use super::JsonValue;
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10pub struct CancellationRequest {
11 #[serde(default, skip_serializing_if = "Option::is_none")]
12 pub reason: Option<String>,
13}
14
15impl CancellationRequest {
16 pub fn new(reason: Option<String>) -> Self {
17 Self { reason }
18 }
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
23pub struct CancellationRequestSnapshot {
24 pub request: CancellationRequest,
25 pub requested_at: DateTime<Utc>,
26 pub sequence: u64,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
31pub struct WorkflowProgress {
32 pub progress_id: String,
33 pub completed: u64,
34 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub total: Option<u64>,
36 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub message: Option<String>,
38 #[serde(default, skip_serializing_if = "JsonValue::is_null")]
39 pub details: JsonValue,
40}
41
42impl WorkflowProgress {
43 pub fn new(progress_id: impl Into<String>, completed: u64) -> Self {
44 Self {
45 progress_id: progress_id.into(),
46 completed,
47 total: None,
48 message: None,
49 details: JsonValue::Null,
50 }
51 }
52
53 pub fn with_total(mut self, total: u64) -> Self {
54 self.total = Some(total);
55 self
56 }
57
58 pub fn with_message(mut self, message: impl Into<String>) -> Self {
59 self.message = Some(message.into());
60 self
61 }
62
63 pub fn with_details(mut self, details: JsonValue) -> Self {
64 self.details = details;
65 self
66 }
67
68 pub(crate) fn validate(&self) -> Result<()> {
69 if self.progress_id.trim().is_empty() {
70 return Err(FlowError::InvalidTransition(
71 "workflow progress id must not be empty".to_string(),
72 ));
73 }
74 if self
75 .total
76 .is_some_and(|total| total == 0 || self.completed > total)
77 {
78 return Err(FlowError::InvalidTransition(format!(
79 "workflow progress {} must satisfy completed <= total and total > 0",
80 self.progress_id
81 )));
82 }
83 Ok(())
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
93pub struct ChildOperationReference {
94 pub reference_id: String,
95 pub kind: String,
96 pub operation_id: String,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub flow_run_id: Option<String>,
99 #[serde(default, skip_serializing_if = "JsonValue::is_null")]
100 pub metadata: JsonValue,
101}
102
103impl ChildOperationReference {
104 pub fn new(
105 reference_id: impl Into<String>,
106 kind: impl Into<String>,
107 operation_id: impl Into<String>,
108 ) -> Self {
109 Self {
110 reference_id: reference_id.into(),
111 kind: kind.into(),
112 operation_id: operation_id.into(),
113 flow_run_id: None,
114 metadata: JsonValue::Null,
115 }
116 }
117
118 pub fn with_flow_run_id(mut self, flow_run_id: impl Into<String>) -> Self {
119 self.flow_run_id = Some(flow_run_id.into());
120 self
121 }
122
123 pub fn with_metadata(mut self, metadata: JsonValue) -> Self {
124 self.metadata = metadata;
125 self
126 }
127
128 pub(crate) fn validate(&self) -> Result<()> {
129 if self.reference_id.trim().is_empty() {
130 return Err(FlowError::InvalidTransition(
131 "child operation reference id must not be empty".to_string(),
132 ));
133 }
134 if self.kind.trim().is_empty() {
135 return Err(FlowError::InvalidTransition(format!(
136 "child operation {} kind must not be empty",
137 self.reference_id
138 )));
139 }
140 if self.operation_id.trim().is_empty() {
141 return Err(FlowError::InvalidTransition(format!(
142 "child operation {} operation id must not be empty",
143 self.reference_id
144 )));
145 }
146 if self
147 .flow_run_id
148 .as_deref()
149 .is_some_and(|run_id| run_id.trim().is_empty())
150 {
151 return Err(FlowError::InvalidTransition(format!(
152 "child operation {} Flow run id must not be empty",
153 self.reference_id
154 )));
155 }
156 Ok(())
157 }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
162#[serde(tag = "type", rename_all = "snake_case")]
163pub enum WorkflowTerminalOutcome {
164 Completed {
165 output: JsonValue,
166 },
167 Failed {
168 error: String,
169 },
170 Cancelled {
171 #[serde(default, skip_serializing_if = "Option::is_none")]
172 reason: Option<String>,
173 },
174 TimedOut {
175 deadline: DateTime<Utc>,
176 #[serde(default, skip_serializing_if = "Option::is_none")]
177 reason: Option<String>,
178 },
179 RetryExhausted {
180 step_id: String,
181 attempt: u32,
182 error: String,
183 },
184 HostShutdown {
185 #[serde(default, skip_serializing_if = "Option::is_none")]
186 reason: Option<String>,
187 },
188}