1use std::fmt;
2
3use thiserror::Error;
4
5use crate::runtime_build::RuntimeBuildId;
6
7pub type Result<T> = std::result::Result<T, FlowError>;
9
10#[derive(Error)]
12#[non_exhaustive]
13pub enum FlowError {
14 #[error("workflow run not found: {0}")]
16 RunNotFound(String),
17
18 #[error("workflow run {0} is already terminal")]
20 RunTerminal(String),
21
22 #[error("workflow run id is invalid: {0}")]
24 InvalidRunId(String),
25
26 #[error("continue-as-new chain contains a cycle at workflow run {0}")]
28 ContinueAsNewCycle(String),
29
30 #[error("continue-as-new chain exceeded the configured limit of {0} hops")]
32 ContinueAsNewLimitExceeded(usize),
33
34 #[error("child workflow graph contains a cycle at workflow run {0}")]
36 ChildWorkflowCycle(String),
37
38 #[error("child workflow nesting exceeded the configured depth of {0}")]
40 ChildWorkflowDepthExceeded(usize),
41
42 #[error("workflow run {run_id} conflicts with existing run: {reason}")]
44 RunConflict {
45 run_id: String,
47 reason: String,
49 },
50
51 #[error("invalid runtime build identity: {0}")]
53 InvalidRuntimeBuildId(String),
54
55 #[error("invalid workflow patch identity: {0}")]
57 InvalidWorkflowPatchId(String),
58
59 #[error(
61 "workflow run {run_id} requires runtime build {required_build_id:?}, but the configured current build is {current_build_id:?}"
62 )]
63 RuntimeBuildUnavailable {
64 run_id: String,
66 required_build_id: Option<RuntimeBuildId>,
68 current_build_id: Option<RuntimeBuildId>,
70 },
71
72 #[error("no Flow task route is registered for runtime build {required_build_id:?}")]
74 RuntimeBuildRouteNotFound {
75 required_build_id: Option<RuntimeBuildId>,
77 },
78
79 #[error("non-deterministic workflow replay for run {run_id}: {reason}")]
81 NonDeterministic {
82 run_id: String,
84 reason: String,
86 },
87
88 #[error(
90 "event sequence conflict for run {run_id}: expected {expected_sequence}, actual {actual_sequence}"
91 )]
92 EventConflict {
93 run_id: String,
95 expected_sequence: u64,
97 actual_sequence: u64,
99 },
100
101 #[error("active hook token not found (value redacted)")]
104 HookTokenNotFound(String),
105
106 #[error("workflow task lease is no longer active: {0}")]
108 LeaseLost(String),
109
110 #[error(
113 "active hook token is already used by run {existing_run_id} hook {existing_hook_id} (value redacted)"
114 )]
115 HookTokenConflict {
116 token: String,
118 existing_run_id: String,
120 existing_hook_id: String,
122 },
123
124 #[error("hook {hook_id} for workflow run {run_id} conflicts with request: {reason}")]
126 HookConflict {
127 run_id: String,
129 hook_id: String,
131 reason: String,
133 },
134
135 #[error("signal {signal_id} for workflow run {run_id} conflicts with request: {reason}")]
137 SignalConflict {
138 run_id: String,
140 signal_id: String,
142 reason: String,
144 },
145
146 #[error("invalid workflow definition: {0}")]
148 InvalidWorkflow(String),
149
150 #[error("invalid state transition: {0}")]
152 InvalidTransition(String),
153
154 #[error("invalid worker configuration: {0}")]
156 InvalidWorkerConfiguration(String),
157
158 #[error("task manager error: {0}")]
160 TaskManagement(String),
161
162 #[error("event store error: {0}")]
164 Store(String),
165
166 #[error("runtime error: {0}")]
168 Runtime(String),
169
170 #[error("serialization error: {0}")]
172 Serialization(#[from] serde_json::Error),
173
174 #[error("io error: {0}")]
176 Io(#[from] std::io::Error),
177
178 #[error("workflow replay exceeded {0} iterations")]
180 ReplayLimitExceeded(usize),
181}
182
183impl fmt::Debug for FlowError {
187 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
188 match self {
189 Self::RunNotFound(run_id) => {
190 formatter.debug_tuple("RunNotFound").field(run_id).finish()
191 }
192 Self::RunTerminal(run_id) => {
193 formatter.debug_tuple("RunTerminal").field(run_id).finish()
194 }
195 Self::InvalidRunId(run_id) => {
196 formatter.debug_tuple("InvalidRunId").field(run_id).finish()
197 }
198 Self::ContinueAsNewCycle(run_id) => formatter
199 .debug_tuple("ContinueAsNewCycle")
200 .field(run_id)
201 .finish(),
202 Self::ContinueAsNewLimitExceeded(limit) => formatter
203 .debug_tuple("ContinueAsNewLimitExceeded")
204 .field(limit)
205 .finish(),
206 Self::ChildWorkflowCycle(run_id) => formatter
207 .debug_tuple("ChildWorkflowCycle")
208 .field(run_id)
209 .finish(),
210 Self::ChildWorkflowDepthExceeded(limit) => formatter
211 .debug_tuple("ChildWorkflowDepthExceeded")
212 .field(limit)
213 .finish(),
214 Self::RunConflict { run_id, reason } => formatter
215 .debug_struct("RunConflict")
216 .field("run_id", run_id)
217 .field("reason", reason)
218 .finish(),
219 Self::InvalidRuntimeBuildId(reason) => formatter
220 .debug_tuple("InvalidRuntimeBuildId")
221 .field(reason)
222 .finish(),
223 Self::InvalidWorkflowPatchId(reason) => formatter
224 .debug_tuple("InvalidWorkflowPatchId")
225 .field(reason)
226 .finish(),
227 Self::RuntimeBuildUnavailable {
228 run_id,
229 required_build_id,
230 current_build_id,
231 } => formatter
232 .debug_struct("RuntimeBuildUnavailable")
233 .field("run_id", run_id)
234 .field("required_build_id", required_build_id)
235 .field("current_build_id", current_build_id)
236 .finish(),
237 Self::RuntimeBuildRouteNotFound { required_build_id } => formatter
238 .debug_struct("RuntimeBuildRouteNotFound")
239 .field("required_build_id", required_build_id)
240 .finish(),
241 Self::NonDeterministic { run_id, reason } => formatter
242 .debug_struct("NonDeterministic")
243 .field("run_id", run_id)
244 .field("reason", reason)
245 .finish(),
246 Self::EventConflict {
247 run_id,
248 expected_sequence,
249 actual_sequence,
250 } => formatter
251 .debug_struct("EventConflict")
252 .field("run_id", run_id)
253 .field("expected_sequence", expected_sequence)
254 .field("actual_sequence", actual_sequence)
255 .finish(),
256 Self::HookTokenNotFound(_) => formatter
257 .debug_tuple("HookTokenNotFound")
258 .field(&"<redacted>")
259 .finish(),
260 Self::LeaseLost(lease_id) => {
261 formatter.debug_tuple("LeaseLost").field(lease_id).finish()
262 }
263 Self::HookTokenConflict {
264 existing_run_id,
265 existing_hook_id,
266 ..
267 } => formatter
268 .debug_struct("HookTokenConflict")
269 .field("token", &"<redacted>")
270 .field("existing_run_id", existing_run_id)
271 .field("existing_hook_id", existing_hook_id)
272 .finish(),
273 Self::HookConflict {
274 run_id,
275 hook_id,
276 reason,
277 } => formatter
278 .debug_struct("HookConflict")
279 .field("run_id", run_id)
280 .field("hook_id", hook_id)
281 .field("reason", reason)
282 .finish(),
283 Self::SignalConflict {
284 run_id,
285 signal_id,
286 reason,
287 } => formatter
288 .debug_struct("SignalConflict")
289 .field("run_id", run_id)
290 .field("signal_id", signal_id)
291 .field("reason", reason)
292 .finish(),
293 Self::InvalidWorkflow(message) => formatter
294 .debug_tuple("InvalidWorkflow")
295 .field(message)
296 .finish(),
297 Self::InvalidTransition(message) => formatter
298 .debug_tuple("InvalidTransition")
299 .field(message)
300 .finish(),
301 Self::InvalidWorkerConfiguration(message) => formatter
302 .debug_tuple("InvalidWorkerConfiguration")
303 .field(message)
304 .finish(),
305 Self::TaskManagement(message) => formatter
306 .debug_tuple("TaskManagement")
307 .field(message)
308 .finish(),
309 Self::Store(message) => formatter.debug_tuple("Store").field(message).finish(),
310 Self::Runtime(message) => formatter.debug_tuple("Runtime").field(message).finish(),
311 Self::Serialization(error) => {
312 formatter.debug_tuple("Serialization").field(error).finish()
313 }
314 Self::Io(error) => formatter.debug_tuple("Io").field(error).finish(),
315 Self::ReplayLimitExceeded(limit) => formatter
316 .debug_tuple("ReplayLimitExceeded")
317 .field(limit)
318 .finish(),
319 }
320 }
321}