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)]
12pub enum FlowError {
13 #[error("workflow run not found: {0}")]
14 RunNotFound(String),
15
16 #[error("workflow run {0} is already terminal")]
17 RunTerminal(String),
18
19 #[error("workflow run id is invalid: {0}")]
20 InvalidRunId(String),
21
22 #[error("workflow run {run_id} conflicts with existing run: {reason}")]
23 RunConflict { run_id: String, reason: String },
24
25 #[error("invalid runtime build identity: {0}")]
26 InvalidRuntimeBuildId(String),
27
28 #[error(
29 "workflow run {run_id} requires runtime build {required_build_id:?}, but the configured current build is {current_build_id:?}"
30 )]
31 RuntimeBuildUnavailable {
32 run_id: String,
33 required_build_id: Option<RuntimeBuildId>,
34 current_build_id: Option<RuntimeBuildId>,
35 },
36
37 #[error("no Flow task route is registered for runtime build {required_build_id:?}")]
38 RuntimeBuildRouteNotFound {
39 required_build_id: Option<RuntimeBuildId>,
40 },
41
42 #[error("non-deterministic workflow replay for run {run_id}: {reason}")]
43 NonDeterministic { run_id: String, reason: String },
44
45 #[error(
46 "event sequence conflict for run {run_id}: expected {expected_sequence}, actual {actual_sequence}"
47 )]
48 EventConflict {
49 run_id: String,
50 expected_sequence: u64,
51 actual_sequence: u64,
52 },
53
54 #[error("active hook token not found (value redacted)")]
57 HookTokenNotFound(String),
58
59 #[error("workflow task lease is no longer active: {0}")]
60 LeaseLost(String),
61
62 #[error(
65 "active hook token is already used by run {existing_run_id} hook {existing_hook_id} (value redacted)"
66 )]
67 HookTokenConflict {
68 token: String,
69 existing_run_id: String,
70 existing_hook_id: String,
71 },
72
73 #[error("hook {hook_id} for workflow run {run_id} conflicts with request: {reason}")]
74 HookConflict {
75 run_id: String,
76 hook_id: String,
77 reason: String,
78 },
79
80 #[error("invalid workflow definition: {0}")]
81 InvalidWorkflow(String),
82
83 #[error("invalid state transition: {0}")]
84 InvalidTransition(String),
85
86 #[error("invalid worker configuration: {0}")]
87 InvalidWorkerConfiguration(String),
88
89 #[error("task manager error: {0}")]
90 TaskManagement(String),
91
92 #[error("event store error: {0}")]
93 Store(String),
94
95 #[error("runtime error: {0}")]
96 Runtime(String),
97
98 #[error("serialization error: {0}")]
99 Serialization(#[from] serde_json::Error),
100
101 #[error("io error: {0}")]
102 Io(#[from] std::io::Error),
103
104 #[error("workflow replay exceeded {0} iterations")]
105 ReplayLimitExceeded(usize),
106}
107
108impl fmt::Debug for FlowError {
112 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
113 match self {
114 Self::RunNotFound(run_id) => {
115 formatter.debug_tuple("RunNotFound").field(run_id).finish()
116 }
117 Self::RunTerminal(run_id) => {
118 formatter.debug_tuple("RunTerminal").field(run_id).finish()
119 }
120 Self::InvalidRunId(run_id) => {
121 formatter.debug_tuple("InvalidRunId").field(run_id).finish()
122 }
123 Self::RunConflict { run_id, reason } => formatter
124 .debug_struct("RunConflict")
125 .field("run_id", run_id)
126 .field("reason", reason)
127 .finish(),
128 Self::InvalidRuntimeBuildId(reason) => formatter
129 .debug_tuple("InvalidRuntimeBuildId")
130 .field(reason)
131 .finish(),
132 Self::RuntimeBuildUnavailable {
133 run_id,
134 required_build_id,
135 current_build_id,
136 } => formatter
137 .debug_struct("RuntimeBuildUnavailable")
138 .field("run_id", run_id)
139 .field("required_build_id", required_build_id)
140 .field("current_build_id", current_build_id)
141 .finish(),
142 Self::RuntimeBuildRouteNotFound { required_build_id } => formatter
143 .debug_struct("RuntimeBuildRouteNotFound")
144 .field("required_build_id", required_build_id)
145 .finish(),
146 Self::NonDeterministic { run_id, reason } => formatter
147 .debug_struct("NonDeterministic")
148 .field("run_id", run_id)
149 .field("reason", reason)
150 .finish(),
151 Self::EventConflict {
152 run_id,
153 expected_sequence,
154 actual_sequence,
155 } => formatter
156 .debug_struct("EventConflict")
157 .field("run_id", run_id)
158 .field("expected_sequence", expected_sequence)
159 .field("actual_sequence", actual_sequence)
160 .finish(),
161 Self::HookTokenNotFound(_) => formatter
162 .debug_tuple("HookTokenNotFound")
163 .field(&"<redacted>")
164 .finish(),
165 Self::LeaseLost(lease_id) => {
166 formatter.debug_tuple("LeaseLost").field(lease_id).finish()
167 }
168 Self::HookTokenConflict {
169 existing_run_id,
170 existing_hook_id,
171 ..
172 } => formatter
173 .debug_struct("HookTokenConflict")
174 .field("token", &"<redacted>")
175 .field("existing_run_id", existing_run_id)
176 .field("existing_hook_id", existing_hook_id)
177 .finish(),
178 Self::HookConflict {
179 run_id,
180 hook_id,
181 reason,
182 } => formatter
183 .debug_struct("HookConflict")
184 .field("run_id", run_id)
185 .field("hook_id", hook_id)
186 .field("reason", reason)
187 .finish(),
188 Self::InvalidWorkflow(message) => formatter
189 .debug_tuple("InvalidWorkflow")
190 .field(message)
191 .finish(),
192 Self::InvalidTransition(message) => formatter
193 .debug_tuple("InvalidTransition")
194 .field(message)
195 .finish(),
196 Self::InvalidWorkerConfiguration(message) => formatter
197 .debug_tuple("InvalidWorkerConfiguration")
198 .field(message)
199 .finish(),
200 Self::TaskManagement(message) => formatter
201 .debug_tuple("TaskManagement")
202 .field(message)
203 .finish(),
204 Self::Store(message) => formatter.debug_tuple("Store").field(message).finish(),
205 Self::Runtime(message) => formatter.debug_tuple("Runtime").field(message).finish(),
206 Self::Serialization(error) => {
207 formatter.debug_tuple("Serialization").field(error).finish()
208 }
209 Self::Io(error) => formatter.debug_tuple("Io").field(error).finish(),
210 Self::ReplayLimitExceeded(limit) => formatter
211 .debug_tuple("ReplayLimitExceeded")
212 .field(limit)
213 .finish(),
214 }
215 }
216}