Skip to main content

qubit_fs/error/
open_failure.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5// =============================================================================
6//! Opening failures that preserve isolated recovery ownership.
7
8use std::error::Error;
9use std::fmt::Debug;
10use std::fmt::Display;
11use std::fmt::Formatter;
12use std::fmt::Result as FmtResult;
13
14use super::FsError;
15use super::OpenFailureStage;
16
17/// A failed open with its causal error and optional isolated recovery session.
18///
19/// `R` owns only explicit cleanup authority. Keep this error or take its
20/// recovery session before reporting failure. Dropping the error does not
21/// confirm cleanup. There is deliberately no conversion into `FsError` that
22/// discards recovery.
23///
24/// # Examples
25///
26/// ```rust
27/// use qubit_fs::error::{FsError, FsErrorKind, FsOperation, OpenFailure, OpenFailureStage};
28///
29/// assert!(std::any::type_name::<OpenFailure<()>>().contains("OpenFailure"));
30/// let error = FsError::new(FsErrorKind::NotFound, FsOperation::OpenReader, "missing");
31/// assert_eq!(FsOperation::OpenReader, error.operation());
32/// assert_eq!(OpenFailureStage::Preflight, OpenFailureStage::Preflight);
33/// ```
34#[must_use = "inspect and retain recovery ownership before abandoning an open failure"]
35pub struct OpenFailure<R> {
36    /// Original contextual failure, unchanged by later recovery.
37    error: Box<FsError>,
38    /// Stage where opening stopped.
39    stage: OpenFailureStage,
40    /// Isolated session, present when a successfully opened envelope was
41    /// rejected.
42    recovery: Option<R>,
43}
44
45impl<R> OpenFailure<R> {
46    /// Creates a failure after classifying the stage and transferring
47    /// ownership.
48    pub(crate) fn new(error: FsError, stage: OpenFailureStage, recovery: Option<R>) -> Self {
49        Self {
50            error: Box::new(error),
51            stage,
52            recovery,
53        }
54    }
55    /// Returns the original failure; cleanup does not rewrite it.
56    pub const fn error(&self) -> &FsError {
57        &self.error
58    }
59    /// Returns the stage where opening stopped.
60    pub const fn stage(&self) -> OpenFailureStage {
61        self.stage
62    }
63    /// Returns the retained recovery session, or None before an envelope
64    /// arrived.
65    pub const fn recovery(&self) -> Option<&R> {
66        self.recovery.as_ref()
67    }
68    /// Borrows the retained session for explicit cleanup, when available.
69    pub fn recovery_mut(&mut self) -> Option<&mut R> {
70        self.recovery.as_mut()
71    }
72    /// Transfers recovery ownership without changing the historical failure.
73    pub fn take_recovery(&mut self) -> Option<R> {
74        self.recovery.take()
75    }
76    /// Returns all failure facts and transfers any recovery ownership.
77    ///
78    /// # Returns
79    /// The original error, opening stage, and optional retained recovery
80    /// session.
81    pub fn into_parts(self) -> (FsError, OpenFailureStage, Option<R>) {
82        (*self.error, self.stage, self.recovery)
83    }
84}
85impl<R> Debug for OpenFailure<R> {
86    /// Formats failure facts without formatting the session or its claimed
87    /// identity.
88    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
89        f.debug_struct("OpenFailure")
90            .field("error", &self.error)
91            .field("stage", &self.stage)
92            .field("has_recovery", &self.recovery.is_some())
93            .finish()
94    }
95}
96impl<R> Display for OpenFailure<R> {
97    /// Formats only the original contextual failure.
98    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
99        Display::fmt(&self.error, f)
100    }
101}
102impl<R: 'static> Error for OpenFailure<R> {
103    /// Exposes the original error chain without surrendering recovery
104    /// ownership.
105    fn source(&self) -> Option<&(dyn Error + 'static)> {
106        Some(self.error.as_ref())
107    }
108}