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