1use std::convert::Infallible;
18#[derive(Debug, thiserror::Error)]
33pub enum Error<E = Infallible> {
34 #[error("runtime shutdown budget expired during {phase}; cleanup remains owned")]
35 ShutdownTimeout { phase: &'static str },
36
37 #[error("authenticated caller identity is required")]
38 Unauthenticated,
39
40 #[error("caller is not permitted to perform this action")]
41 PermissionDenied,
42
43 #[error("record protobuf schema is invalid: {source}")]
44 RecordSchema {
45 #[source]
46 source: crate::schema::Error,
47 },
48
49 #[error("record protobuf payload is invalid: {source}")]
50 RecordPayload {
51 #[source]
52 source: crate::schema::PayloadError,
53 },
54
55 #[error("record declaration is unavailable: {source}")]
56 RecordDeclaration {
57 #[source]
58 source: crate::state::DecodeError,
59 },
60
61 #[error(transparent)]
62 OperationRefused(#[from] crate::Refusal),
63
64 #[error("operation child lane `{child}` would cycle with held lane `{parent}`")]
65 OperationLaneCycle { parent: &'static str, child: &'static str },
66
67 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
68 #[error("operation caller is unavailable: {source}")]
69 OperationCaller {
70 #[source]
71 source: crate::plugin::DispatchError,
72 },
73
74 #[error("caller `{caller}` may not invoke operation `{operation}` on `{owner}`")]
75 OperationUnauthorized { caller: String, owner: String, operation: String },
76
77 #[error("operation `{operation}` payload is unreadable: {source}")]
78 OperationPayload {
79 operation: String,
80
81 #[source]
82 source: harmos_domain::MessageError,
83 },
84
85 #[error("operation `{operation}` panicked")]
86 OperationPanicked { operation: String },
87
88 #[error("operation declaration is invalid: {source}")]
89 OperationDeclaration {
90 #[source]
91 source: harmos_domain::Error,
92 },
93
94 #[error("operation `{operation}` failed")]
96 OperationFailed {
97 operation: String,
98
99 #[source]
100 source: std::sync::Arc<dyn std::error::Error + Send + Sync>,
101 },
102
103 #[error("operation `{operation}` could not finalize its invocation state")]
104 OperationFinalization {
105 operation: String,
106 outcome: Option<std::sync::Arc<Error>>,
107
108 #[source]
109 source: std::sync::Arc<Error>,
110 },
111
112 #[error("operation physical settlement failed: {source}")]
113 OperationSettlement {
114 #[source]
115 source: std::sync::Arc<Error>,
116 },
117
118 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
119 #[error("plugin lifecycle failed: {source}")]
120 PluginLifecycle {
121 #[source]
122 source: Box<crate::plugin::PluginFailure>,
123 },
124
125 #[error("{domain} task failed: {source}")]
126 TaskFailed {
127 domain: &'static str,
128
129 #[source]
130 source: std::sync::Arc<dyn std::error::Error + Send + Sync>,
131 },
132
133 #[error("{domain} is not ready")]
134 NotReady { domain: &'static str },
135
136 #[error("persistence failed: {source}")]
137 Persistence {
138 #[source]
139 source: std::sync::Arc<Error>,
140 },
141
142 #[error("{domain} admission is closed")]
143 Closing { domain: &'static str },
144
145 #[error("{domain} `{name}` is already registered")]
146 AlreadyRegistered { domain: &'static str, name: String },
147
148 #[error("{domain} `{name}` is not registered")]
149 NotRegistered { domain: &'static str, name: String },
150
151 #[error("backend {operation} failed ({kind:?}): {source}")]
152 Backend {
153 operation: &'static str,
154 kind: BackendFailure,
155
156 #[source]
157 source: Box<dyn std::error::Error + Send + Sync>,
158 },
159
160 #[error("invalid {field}: {reason}")]
161 InvalidInput { field: &'static str, reason: &'static str },
162
163 #[error("{resource} capacity is exhausted")]
164 ResourceExhausted { resource: &'static str },
165
166 #[error("positions belong to different store histories")]
167 DifferentHistory,
168
169 #[error("the read view has expired")]
170 ViewExpired,
171
172 #[error("retained data begins at {available}, but {requested} was requested")]
173 Gap { requested: u64, available: u64 },
174
175 #[error("required journal capture failed")]
176 CaptureFailed,
177
178 #[error("the requested state boundary splits a logical commit")]
179 IncompleteCommit,
180
181 #[error("the logical commit could not be prepared: {0}")]
182 Preparation(PreparationError),
183
184 #[error("the transaction was refused: {0}")]
185 Rejected(E),
186
187 #[error("the undo conflicts with the current state: {0}")]
188 UndoConflict(E),
189
190 #[error("the redo conflicts with the current state: {0}")]
191 RedoConflict(E),
192
193 #[error("this principal has nothing to undo in scope")]
194 NothingToUndo,
195
196 #[error("this principal has nothing to redo in scope")]
197 NothingToRedo,
198
199 #[error("the supplied origin is above the requested position")]
200 OriginAhead,
201
202 #[error("the requested query frontier is above the applied position")]
203 FrontierAhead,
204
205 #[error("the requested position is beneath the retained-history horizon")]
206 BeneathHorizon,
207
208 #[error("the requested position is below the in-memory tail's floor")]
209 BeyondTail,
210
211 #[error("the journal is not admitting requests")]
212 Unavailable,
213}
214
215pub use harmos_storage::BackendFailure;
216
217pub use harmos_domain::state::PreparationError;
218
219impl<E> From<harmos_storage::Error> for Error<E> {
224 fn from(error: harmos_storage::Error) -> Self {
225 use harmos_storage::Error as Storage;
226 match error {
227 Storage::OperationFailed { operation, source } => Self::OperationFailed { operation, source },
228 Storage::NotReady { domain } => Self::NotReady { domain },
229 Storage::Persistence { source } => Self::Persistence {
230 source: std::sync::Arc::new(std::sync::Arc::try_unwrap(source).map(Error::from).unwrap_or_else(|source| {
231 Error::backend("storage persistence", source.backend_failure().unwrap_or(BackendFailure::Permanent), source)
232 })),
233 },
234 Storage::Closing { domain } => Self::Closing { domain },
235 Storage::AlreadyRegistered { domain, name } => Self::AlreadyRegistered { domain, name },
236 Storage::Backend { operation, kind, source } => Self::Backend { operation, kind, source },
237 Storage::InvalidInput { field, reason } => Self::InvalidInput { field, reason },
238 Storage::ResourceExhausted { resource } => Self::ResourceExhausted { resource },
239 Storage::Gap { requested, available } => Self::Gap { requested, available },
240 Storage::Domain(source) => source.into(),
241 }
242 }
243}
244
245impl<E> From<harmos_domain::Error> for Error<E> {
246 fn from(error: harmos_domain::Error) -> Self {
247 match error {
248 harmos_domain::Error::InvalidInput { field, reason } => Self::InvalidInput { field, reason },
249 harmos_domain::Error::ResourceExhausted { resource } => Self::ResourceExhausted { resource },
250 harmos_domain::Error::DifferentHistory => Self::DifferentHistory,
251 harmos_domain::Error::Corrupt { operation, source } => Self::backend(operation, BackendFailure::Corrupt, source),
252 source => Self::OperationDeclaration { source },
253 }
254 }
255}
256
257impl<E> From<harmos_domain::operations::ContextError> for Error<E> {
258 fn from(error: harmos_domain::operations::ContextError) -> Self {
259 use harmos_domain::operations::ContextError;
260 match error {
261 ContextError::Duplicate { name } => Self::AlreadyRegistered { domain: "call context", name },
262 ContextError::Missing { name } => Self::NotRegistered { domain: "call context", name },
263 ContextError::Decode { .. } => Self::InvalidInput { field: "call context", reason: "portable value is unreadable" },
264 ContextError::Budget => Self::InvalidInput { field: "call context", reason: "portable values exceed their budget" },
265 }
266 }
267}
268
269impl<E> Error<E> {
270 pub fn failure(&self) -> Option<&harmos_domain::Failure> {
273 match self {
274 Self::OperationFailed { source, .. } | Self::TaskFailed { source, .. } => carried(source.as_ref()),
275 Self::OperationFinalization { outcome, source, .. } => outcome.as_deref().and_then(Error::failure).or_else(|| source.failure()),
277 Self::OperationSettlement { source } => source.failure(),
278 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
279 Self::PluginLifecycle { source } => carried(source.as_ref()),
280 _ => None,
281 }
282 }
283
284 pub fn backend(operation: &'static str, kind: BackendFailure, source: impl std::error::Error + Send + Sync + 'static) -> Self {
285 Self::Backend { operation, kind, source: Box::new(source) }
286 }
287}
288
289fn carried<'a>(source: &'a (dyn std::error::Error + 'static)) -> Option<&'a harmos_domain::Failure> {
296 std::iter::successors(Some(source), |link| link.source()).find_map(|link| {
297 if let Some(error) = link.downcast_ref::<Error>() {
298 return error.failure();
299 }
300 #[cfg(feature = "plugin-sidecar")]
301 if let Some(error) = link.downcast_ref::<crate::operations::NativeError>() {
302 return error.failure();
303 }
304 #[cfg(all(feature = "plugin-guest", not(target_arch = "wasm32")))]
305 if let Some(error) = link.downcast_ref::<crate::operations::ComponentError>() {
306 return error.failure();
307 }
308 harmos_domain::Failure::find(link)
309 })
310}
311
312