Skip to main content

aion_server/
error.rs

1//! `ServerError` taxonomy for server library modules.
2
3use std::borrow::Cow;
4use std::net::SocketAddr;
5use std::path::PathBuf;
6
7use aion::EngineError;
8use aion_proto::WireError;
9use aion_store::StoreError;
10use thiserror::Error;
11
12#[path = "error_engine.rs"]
13mod engine;
14#[path = "error_process_exit.rs"]
15mod process_exit;
16
17/// Server-library error taxonomy.
18#[derive(Debug, Error)]
19pub enum ServerError {
20    /// Operator configuration could not be loaded or validated.
21    #[error("configuration error: {message}")]
22    Config {
23        /// Redacted, operator-facing failure message.
24        message: String,
25    },
26
27    /// A path-ambient store backend was configured beneath a renameable directory.
28    #[error(
29        "unsafe store.data_dir `{}`: ancestor `{}` is not owner-controlled: {reason}; \
30         move store.data_dir beneath the private Aion home (`$AION_HOME`, default `~/.aion`) \
31         and keep its ancestor chain owner-only",
32        .data_root.display(),
33        .component.display()
34    )]
35    UnsafeDataRootAncestor {
36        /// Descriptor-resolved data root that the backend would use by pathname.
37        data_root: PathBuf,
38        /// First unsafe component in the resolved root's ancestor chain.
39        component: PathBuf,
40        /// Ownership, mode, or inspection failure that made the component unsafe.
41        reason: String,
42    },
43
44    /// A transport listener could not bind or start.
45    #[error("{transport} transport failed at {address}: {message}")]
46    TransportBind {
47        /// Transport name.
48        transport: &'static str,
49        /// Configured listener address.
50        address: SocketAddr,
51        /// Redacted, operator-facing failure message.
52        message: String,
53    },
54
55    /// A running transport task aborted: it panicked or was cancelled.
56    #[error("{transport} transport task failed: {message}")]
57    Transport {
58        /// Transport name.
59        transport: &'static str,
60        /// Redacted, operator-facing failure message.
61        message: String,
62    },
63
64    /// A termination-signal listener could not be installed or failed.
65    #[error("{listener} listener failed: {message}")]
66    SignalListener {
67        /// Listener name (`SIGTERM`, `SIGINT`, or the portable fallback).
68        listener: &'static str,
69        /// Redacted, operator-facing failure message.
70        message: String,
71    },
72
73    /// Namespace validation or authorization failed.
74    #[error("namespace error: {message}")]
75    Namespace {
76        /// Redacted namespace failure message.
77        message: String,
78    },
79
80    /// Engine call failed.
81    #[error("engine call failed: {source}")]
82    EngineCall {
83        /// Typed engine error returned by the embedded engine.
84        #[from]
85        source: EngineError,
86    },
87
88    /// Store backend call failed before an engine handle was available.
89    #[error("store backend failed: {source}")]
90    StoreBackend {
91        /// Typed store error returned by the configured backend.
92        #[from]
93        source: StoreError,
94    },
95
96    /// Streaming failure.
97    #[error("stream failure: {failure}")]
98    Stream {
99        /// Stream failure class.
100        failure: StreamFailure,
101    },
102
103    /// A scheduled activity could not be pushed to a worker.
104    #[error(
105        "worker dispatch failed for namespace {namespace}, activity type {activity_type}: {reason}"
106    )]
107    WorkerDispatch {
108        /// Namespace scoped before dispatch.
109        namespace: String,
110        /// Activity type requested by the engine.
111        activity_type: String,
112        /// Redacted dispatch failure reason.
113        reason: String,
114    },
115
116    /// The worker connection chosen for a dispatch was lost mid-flight: the
117    /// connection was already gone at push time, or it closed before the worker
118    /// sent its correlated push reply.
119    ///
120    /// This is DISTINCT from [`Self::WorkerDispatch`]: a `WorkerDispatch` covers a
121    /// genuine reply timeout (the worker is alive but slow), a no-worker-available
122    /// selection failure, or any other dispatch fault, all of which keep the
123    /// outbox's normal exponential backoff. A `WorkerConnectionLost` instead means
124    /// the chosen worker is gone (and has already been deregistered by liminal's
125    /// `on_worker_unregistered`), so the row can be re-armed for IMMEDIATE re-claim
126    /// to fail over to a live worker without waiting out the backoff. The outbox
127    /// dispatcher keys its fast-failover decision on this variant.
128    #[error("worker connection lost during dispatch on {channel}: {detail}")]
129    WorkerConnectionLost {
130        /// Row-derived dispatch channel for operator diagnostics.
131        channel: String,
132        /// Redacted, operator-facing description of how the connection was lost.
133        detail: String,
134    },
135
136    /// A lock was poisoned and the protected state cannot be trusted.
137    #[error("{resource} lock was poisoned")]
138    LockPoisoned {
139        /// Protected resource name.
140        resource: &'static str,
141    },
142
143    /// A failure already translated into the public wire taxonomy.
144    #[error("wire error: {wire}")]
145    Wire {
146        /// Stable wire error.
147        wire: WireError,
148    },
149}
150
151/// Bounded-stream and connection failure classes.
152#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
153pub enum StreamFailure {
154    /// Bounded per-connection buffer overflowed because the consumer lagged.
155    #[error("consumer lagged behind bounded buffer")]
156    Lagged,
157    /// Subscriber closed the connection.
158    #[error("subscriber connection closed")]
159    Closed,
160    /// Upstream engine event stream ended unexpectedly.
161    #[error("engine event stream closed")]
162    UpstreamClosed,
163}
164
165impl From<WireError> for ServerError {
166    fn from(wire: WireError) -> Self {
167        Self::Wire { wire }
168    }
169}
170
171impl ServerError {
172    /// Convert a server error that crosses a transport boundary into the stable
173    /// public wire taxonomy.
174    #[must_use]
175    pub fn to_wire_error(&self) -> WireError {
176        match self {
177            Self::Config { .. }
178            | Self::UnsafeDataRootAncestor { .. }
179            | Self::TransportBind { .. }
180            | Self::Transport { .. }
181            | Self::SignalListener { .. }
182            | Self::LockPoisoned { .. } => WireError::backend("server backend failure"),
183            Self::WorkerDispatch { .. } => WireError::backend("worker dispatch failed"),
184            Self::WorkerConnectionLost { .. } => {
185                WireError::backend("worker connection lost during dispatch")
186            }
187            Self::Namespace { message } => WireError::namespace_denied(message.clone()),
188            Self::EngineCall { source } => wire_from_engine(source),
189            Self::StoreBackend { source } => wire_from_store(source),
190            Self::Stream { failure } => match failure {
191                StreamFailure::Lagged => WireError::lagged("subscriber lagged behind"),
192                StreamFailure::Closed | StreamFailure::UpstreamClosed => {
193                    WireError::backend("event stream closed")
194                }
195            },
196            Self::Wire { wire } => wire.clone(),
197        }
198    }
199
200    /// Return true when this is an operator configuration failure.
201    #[must_use]
202    pub const fn is_config(&self) -> bool {
203        matches!(
204            self,
205            Self::Config { .. } | Self::UnsafeDataRootAncestor { .. }
206        )
207    }
208
209    /// Construct a namespace-denied error without embedding authorization logic.
210    #[must_use]
211    pub fn namespace_denied(message: impl Into<String>) -> Self {
212        Self::Namespace {
213            message: message.into(),
214        }
215    }
216
217    /// Construct the loud, whole-registration rejection when a worker's advertised
218    /// `node` violates a `Pinned{L}` namespace's placement (Control-Plane Phase 2,
219    /// P2-I1). Names the offending namespace, the worker's advertised node (or
220    /// "none"), and the required label set, so the operator sees exactly why the
221    /// registration was refused. Carried on the namespace-denied wire code — a
222    /// registration refused on isolation grounds is a namespace-authorization
223    /// failure, not a transient dispatch error.
224    #[must_use]
225    pub fn placement_admission_denied(
226        namespace: &str,
227        worker_node: Option<&str>,
228        required: &std::collections::BTreeSet<String>,
229    ) -> Self {
230        let node = worker_node.unwrap_or("none");
231        let required = required
232            .iter()
233            .map(String::as_str)
234            .collect::<Vec<_>>()
235            .join(", ");
236        Self::namespace_denied(format!(
237            "worker registration rejected: namespace {namespace} is Pinned to node label(s) \
238             [{required}] but the worker advertises node {node}, which is not in the required set"
239        ))
240    }
241
242    /// Construct a deploy-authorization denial carried on the dedicated
243    /// `deploy_denied` wire code (deploy is not a namespace operation).
244    #[must_use]
245    pub fn deploy_denied(message: impl Into<String>) -> Self {
246        Self::Wire {
247            wire: WireError::deploy_denied(message),
248        }
249    }
250
251    /// Construct a lagged-stream error.
252    #[must_use]
253    pub const fn lagged_stream() -> Self {
254        Self::Stream {
255            failure: StreamFailure::Lagged,
256        }
257    }
258
259    /// Construct a worker-dispatch error.
260    #[must_use]
261    pub fn worker_dispatch(
262        namespace: impl Into<String>,
263        activity_type: impl Into<String>,
264        reason: impl Into<String>,
265    ) -> Self {
266        Self::WorkerDispatch {
267            namespace: namespace.into(),
268            activity_type: activity_type.into(),
269            reason: reason.into(),
270        }
271    }
272
273    /// Construct a worker-connection-lost error for a dispatch whose chosen
274    /// worker connection was gone at push time or closed before replying.
275    #[must_use]
276    pub fn worker_connection_lost(channel: impl Into<String>, detail: impl Into<String>) -> Self {
277        Self::WorkerConnectionLost {
278            channel: channel.into(),
279            detail: detail.into(),
280        }
281    }
282
283    /// Return true when this is a lost-worker-connection dispatch failure.
284    ///
285    /// The outbox dispatcher keys its fast cross-node failover on this: a lost
286    /// connection means the worker is gone (already deregistered), so the row is
287    /// re-armed for immediate re-claim instead of waiting out the retry backoff.
288    #[must_use]
289    pub const fn is_worker_connection_lost(&self) -> bool {
290        matches!(self, Self::WorkerConnectionLost { .. })
291    }
292
293    /// Construct a lock-poison error at the lock boundary.
294    #[must_use]
295    pub const fn lock_poisoned(resource: &'static str) -> Self {
296        Self::LockPoisoned { resource }
297    }
298}
299
300/// Stable structured error metadata for tracing events.
301#[derive(Clone)]
302pub struct ErrorTraceFields<'a> {
303    /// Outer error type recorded in the `error_type` tracing field.
304    pub error_type: Cow<'a, str>,
305    /// Optional inner store error type for `StoreError` records.
306    pub store_error_type: Option<&'static str>,
307    /// Human-readable reason safe for operator logs.
308    pub reason: &'a dyn std::fmt::Display,
309}
310
311impl ServerError {
312    /// Return stable typed fields for structured error logging.
313    #[must_use]
314    pub fn trace_fields(&self) -> ErrorTraceFields<'_> {
315        match self {
316            Self::Config { message } => ErrorTraceFields {
317                error_type: Cow::Borrowed("Config"),
318                store_error_type: None,
319                reason: message,
320            },
321            Self::UnsafeDataRootAncestor { reason, .. } => ErrorTraceFields {
322                error_type: Cow::Borrowed("UnsafeDataRootAncestor"),
323                store_error_type: None,
324                reason,
325            },
326            Self::TransportBind { message, .. } => ErrorTraceFields {
327                error_type: Cow::Borrowed("TransportBind"),
328                store_error_type: None,
329                reason: message,
330            },
331            Self::Transport { message, .. } => ErrorTraceFields {
332                error_type: Cow::Borrowed("Transport"),
333                store_error_type: None,
334                reason: message,
335            },
336            Self::SignalListener { message, .. } => ErrorTraceFields {
337                error_type: Cow::Borrowed("SignalListener"),
338                store_error_type: None,
339                reason: message,
340            },
341            Self::Namespace { message } => ErrorTraceFields {
342                error_type: Cow::Borrowed("Namespace"),
343                store_error_type: None,
344                reason: message,
345            },
346            Self::EngineCall { source } => engine_trace_fields(source),
347            Self::StoreBackend { source } => store_trace_fields(source),
348            Self::Stream { failure } => ErrorTraceFields {
349                error_type: Cow::Borrowed("Stream"),
350                store_error_type: None,
351                reason: failure,
352            },
353            Self::WorkerDispatch { reason, .. } => ErrorTraceFields {
354                error_type: Cow::Borrowed("WorkerDispatch"),
355                store_error_type: None,
356                reason,
357            },
358            Self::WorkerConnectionLost { detail, .. } => ErrorTraceFields {
359                error_type: Cow::Borrowed("WorkerConnectionLost"),
360                store_error_type: None,
361                reason: detail,
362            },
363            Self::LockPoisoned { resource } => ErrorTraceFields {
364                error_type: Cow::Borrowed("LockPoisoned"),
365                store_error_type: None,
366                reason: resource,
367            },
368            Self::Wire { wire } => ErrorTraceFields {
369                error_type: wire
370                    .error_type
371                    .as_deref()
372                    .map_or_else(|| Cow::Borrowed(wire.code.as_str()), Cow::Borrowed),
373                store_error_type: None,
374                reason: wire,
375            },
376        }
377    }
378}
379
380fn engine_trace_fields(source: &EngineError) -> ErrorTraceFields<'_> {
381    match source {
382        EngineError::WorkflowNotFound { .. } => simple_engine_fields("WorkflowNotFound", source),
383        EngineError::InvalidState { .. } => simple_engine_fields("InvalidState", source),
384        EngineError::ScheduleNotFound { .. } => simple_engine_fields("ScheduleNotFound", source),
385        EngineError::ShuttingDown => simple_engine_fields("ShuttingDown", source),
386        EngineError::Store(store) => store_trace_fields(store),
387        EngineError::Durability(durability) => match durability {
388            aion::durability::DurabilityError::Store(store) => store_trace_fields(store),
389            aion::durability::DurabilityError::NonDeterminism(_)
390            | aion::durability::DurabilityError::HistoryShape { .. }
391            | aion::durability::DurabilityError::SearchAttribute(_) => {
392                simple_engine_fields("Durability", source)
393            }
394        },
395        EngineError::MissingStore => simple_engine_fields("MissingStore", source),
396        EngineError::MissingVisibilityStore => {
397            simple_engine_fields("MissingVisibilityStore", source)
398        }
399        EngineError::ConflictingEventPublisher => {
400            simple_engine_fields("ConflictingEventPublisher", source)
401        }
402        EngineError::EventStreaming(_) => simple_engine_fields("EventStreaming", source),
403        EngineError::Load { .. } => simple_engine_fields("Load", source),
404        EngineError::UnknownVersion { .. } => simple_engine_fields("UnknownVersion", source),
405        EngineError::VersionPinned { .. } => simple_engine_fields("VersionPinned", source),
406        EngineError::RouteActive { .. } => simple_engine_fields("RouteActive", source),
407        EngineError::ManifestMismatch { .. } => simple_engine_fields("ManifestMismatch", source),
408        EngineError::Package(_) => simple_engine_fields("Package", source),
409        EngineError::Schedule { .. } => simple_engine_fields("Schedule", source),
410        EngineError::Runtime { .. } => simple_engine_fields("Runtime", source),
411        EngineError::Gate3BifReplacementMissing { .. } => {
412            simple_engine_fields("Gate3BifReplacementMissing", source)
413        }
414        EngineError::CleanupExecutorPoisoned => {
415            simple_engine_fields("CleanupExecutorPoisoned", source)
416        }
417        EngineError::CleanupExecutorShutdownTimedOut { .. } => {
418            simple_engine_fields("CleanupExecutorShutdownTimedOut", source)
419        }
420        EngineError::ProcessExitRegistryPoisoned => {
421            simple_engine_fields("ProcessExitRegistryPoisoned", source)
422        }
423        EngineError::ProcessExitOwnershipPoisoned { .. } => {
424            simple_engine_fields("ProcessExitOwnershipPoisoned", source)
425        }
426        EngineError::ProcessExitStatePoisoned { .. } => {
427            process_exit::trace("ProcessExitStatePoisoned", source)
428        }
429        EngineError::ProcessExitSubscriptionUnavailable => {
430            process_exit::trace("ProcessExitSubscriptionUnavailable", source)
431        }
432        EngineError::ProcessExitDrainerSpawn { .. } => {
433            process_exit::trace("ProcessExitDrainerSpawn", source)
434        }
435        EngineError::ProcessExitDrainerPoisoned => {
436            process_exit::trace("ProcessExitDrainerPoisoned", source)
437        }
438        EngineError::ProcessExitOutcomeMissingAfterEvent { .. } => {
439            process_exit::trace("ProcessExitOutcomeMissingAfterEvent", source)
440        }
441        EngineError::ProcessExitEventStreamDisconnected => {
442            process_exit::trace("ProcessExitEventStreamDisconnected", source)
443        }
444        EngineError::ProcessExitDrainerShutdownTimedOut { .. } => {
445            process_exit::trace("ProcessExitDrainerShutdownTimedOut", source)
446        }
447        EngineError::ProcessExitDrainerPanicked => {
448            process_exit::trace("ProcessExitDrainerPanicked", source)
449        }
450        EngineError::ProcessExitCallbackDispatcherPoisoned
451        | EngineError::ProcessExitCallbackDispatcherUnavailable
452        | EngineError::ProcessExitCallbackDispatcherShutdownTimedOut { .. } => {
453            process_exit::callback_trace(source)
454        }
455        EngineError::ProcessExitAlreadyTerminal { .. } => {
456            simple_engine_fields("ProcessExitAlreadyTerminal", source)
457        }
458        EngineError::ActivityDeliveryPoisoned { .. } => {
459            simple_engine_fields("ActivityDeliveryPoisoned", source)
460        }
461        EngineError::RegistryPoisoned => simple_engine_fields("RegistryPoisoned", source),
462        EngineError::CatalogPoisoned => simple_engine_fields("CatalogPoisoned", source),
463        EngineError::NifRegistration { .. } => simple_engine_fields("NifRegistration", source),
464        EngineError::SignalRouter(_) => simple_engine_fields("SignalRouter", source),
465        EngineError::Query(query) => simple_engine_fields(engine::query_error_type(query), source),
466    }
467}
468
469fn simple_engine_fields<'a>(
470    error_type: &'static str,
471    source: &'a EngineError,
472) -> ErrorTraceFields<'a> {
473    ErrorTraceFields {
474        error_type: Cow::Borrowed(error_type),
475        store_error_type: None,
476        reason: source,
477    }
478}
479
480fn store_trace_fields(source: &StoreError) -> ErrorTraceFields<'_> {
481    ErrorTraceFields {
482        error_type: Cow::Borrowed("StoreError"),
483        store_error_type: Some(store_error_type(source)),
484        reason: source,
485    }
486}
487
488fn store_error_type(source: &StoreError) -> &'static str {
489    match source {
490        StoreError::SequenceConflict { .. } => "SequenceConflict",
491        StoreError::NotFound { .. } => "NotFound",
492        StoreError::NotOwner { .. } => "NotOwner",
493        StoreError::Backend(_) => "Backend",
494        StoreError::Serialization(_) => "Serialization",
495    }
496}
497
498fn wire_from_engine(source: &EngineError) -> WireError {
499    use EngineError as E;
500    use engine::backend_wire as backend;
501
502    match source {
503        EngineError::WorkflowNotFound { .. } => {
504            WireError::not_found_with_type("WorkflowNotFound", source.to_string())
505        }
506        // Reopen precondition failure (AD-012): the run is not a reopenable
507        // terminal. Carried on the dedicated `invalid_state` wire code (gRPC
508        // FailedPrecondition / HTTP 409), distinct from NotFound and Backend.
509        EngineError::InvalidState { reason } => engine::invalid_state_wire(reason),
510        EngineError::ScheduleNotFound { .. } => {
511            WireError::not_found_with_type("ScheduleNotFound", source.to_string())
512        }
513        EngineError::ShuttingDown => {
514            WireError::not_running_with_type("ShuttingDown", source.to_string())
515        }
516        EngineError::Store(store) => wire_from_store(store),
517        EngineError::Durability(durability) => engine::durability_wire(durability, source),
518        EngineError::MissingStore => engine::backend_wire("MissingStore", source),
519        E::MissingVisibilityStore => backend("MissingVisibilityStore", source),
520        E::ConflictingEventPublisher => backend("ConflictingEventPublisher", source),
521        EngineError::EventStreaming(_) => engine::backend_wire("EventStreaming", source),
522        EngineError::Load { .. } => WireError::backend_with_type("Load", source.to_string()),
523        // Deploy-surface refusals (the §2.4 mapping table): unknown
524        // `(type, version)` is not-found; route-active and pinned versions
525        // are state conflicts carried by the dedicated `version_pinned`
526        // code; a same-hash-different-manifest archive is invalid input.
527        EngineError::UnknownVersion { .. } => {
528            WireError::not_found_with_type("UnknownVersion", source.to_string())
529        }
530        EngineError::VersionPinned { .. } => {
531            WireError::version_pinned(source.to_string()).with_error_type("VersionPinned")
532        }
533        EngineError::RouteActive { .. } => {
534            WireError::version_pinned(source.to_string()).with_error_type("RouteActive")
535        }
536        EngineError::ManifestMismatch { .. } => {
537            WireError::invalid_input(source.to_string()).with_error_type("ManifestMismatch")
538        }
539        EngineError::Package(_) => WireError::backend_with_type("Package", source.to_string()),
540        EngineError::Schedule { .. } => {
541            WireError::backend_with_type("Schedule", source.to_string())
542        }
543        EngineError::Runtime { .. } => WireError::backend_with_type("Runtime", source.to_string()),
544        E::Gate3BifReplacementMissing { .. } => backend("Gate3BifReplacementMissing", source),
545        EngineError::CleanupExecutorPoisoned => {
546            WireError::backend_with_type("CleanupExecutorPoisoned", source.to_string())
547        }
548        EngineError::CleanupExecutorShutdownTimedOut { .. } => {
549            WireError::backend_with_type("CleanupExecutorShutdownTimedOut", source.to_string())
550        }
551        EngineError::ProcessExitRegistryPoisoned => {
552            WireError::backend_with_type("ProcessExitRegistryPoisoned", source.to_string())
553        }
554        EngineError::ProcessExitOwnershipPoisoned { .. } => {
555            WireError::backend_with_type("ProcessExitOwnershipPoisoned", source.to_string())
556        }
557        EngineError::ProcessExitStatePoisoned { .. } => {
558            process_exit::wire("ProcessExitStatePoisoned", source)
559        }
560        EngineError::ProcessExitSubscriptionUnavailable => {
561            process_exit::wire("ProcessExitSubscriptionUnavailable", source)
562        }
563        EngineError::ProcessExitDrainerSpawn { .. } => {
564            process_exit::wire("ProcessExitDrainerSpawn", source)
565        }
566        EngineError::ProcessExitDrainerPoisoned => {
567            process_exit::wire("ProcessExitDrainerPoisoned", source)
568        }
569        EngineError::ProcessExitOutcomeMissingAfterEvent { .. } => {
570            process_exit::wire("ProcessExitOutcomeMissingAfterEvent", source)
571        }
572        EngineError::ProcessExitEventStreamDisconnected => {
573            process_exit::wire("ProcessExitEventStreamDisconnected", source)
574        }
575        EngineError::ProcessExitDrainerShutdownTimedOut { .. } => {
576            process_exit::wire("ProcessExitDrainerShutdownTimedOut", source)
577        }
578        EngineError::ProcessExitDrainerPanicked => {
579            process_exit::wire("ProcessExitDrainerPanicked", source)
580        }
581        EngineError::ProcessExitCallbackDispatcherPoisoned
582        | EngineError::ProcessExitCallbackDispatcherUnavailable
583        | EngineError::ProcessExitCallbackDispatcherShutdownTimedOut { .. } => {
584            process_exit::callback_wire(source)
585        }
586        EngineError::ProcessExitAlreadyTerminal { .. } => {
587            WireError::backend_with_type("ProcessExitAlreadyTerminal", source.to_string())
588        }
589        EngineError::ActivityDeliveryPoisoned { .. } => {
590            WireError::backend_with_type("ActivityDeliveryPoisoned", source.to_string())
591        }
592        EngineError::CatalogPoisoned => {
593            WireError::backend_with_type("CatalogPoisoned", source.to_string())
594        }
595        EngineError::RegistryPoisoned => {
596            WireError::backend_with_type("RegistryPoisoned", source.to_string())
597        }
598        EngineError::NifRegistration { .. } => {
599            WireError::backend_with_type("NifRegistration", source.to_string())
600        }
601        EngineError::SignalRouter(_) => {
602            WireError::backend_with_type("SignalRouter", source.to_string())
603        }
604        EngineError::Query(query) => engine::query_wire(query, source),
605    }
606}
607
608fn wire_from_store(source: &StoreError) -> WireError {
609    match source {
610        StoreError::SequenceConflict { .. } => WireError::new_with_type(
611            aion_proto::WireErrorCode::SequenceConflict,
612            "SequenceConflict",
613            source.to_string(),
614        ),
615        StoreError::NotFound { .. } => {
616            WireError::not_found_with_type("NotFound", source.to_string())
617        }
618        StoreError::NotOwner { .. } => {
619            WireError::not_owner(source.to_string()).with_error_type("NotOwner")
620        }
621        StoreError::Backend(_) => WireError::backend_with_type("Backend", source.to_string()),
622        StoreError::Serialization(_) => {
623            WireError::backend_with_type("Serialization", source.to_string())
624        }
625    }
626}
627
628#[cfg(test)]
629#[path = "error_tests.rs"]
630mod tests;