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