Skip to main content

qubit_fs/write/
write_all_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//! Failure returned by the convenience whole-file write operation.
9
10use std::error::Error;
11use std::fmt::Display;
12use std::fmt::Formatter;
13use std::fmt::Result as FmtResult;
14
15use crate::error::FsError;
16use crate::write::WriteFailureState;
17use crate::write::WriterRecovery;
18
19/// A whole-file write failure retaining the recoverable writer when available.
20///
21/// # Examples
22///
23/// ```rust
24/// use qubit_fs::error::{FsError, FsErrorKind, FsOperation};
25/// use qubit_fs::write::{WriteAllFailure, WriteFailureState};
26///
27/// assert!(std::any::type_name::<WriteAllFailure>().contains("WriteAllFailure"));
28/// let error = FsError::new(FsErrorKind::Io, FsOperation::Write, "failed");
29/// assert_eq!(WriteFailureState::NotPublished, WriteFailureState::NotPublished);
30/// assert_eq!(FsOperation::Write, error.operation());
31/// ```
32pub struct WriteAllFailure {
33    /// Contextual filesystem error that interrupted the whole-file write.
34    error: Box<FsError>,
35    /// Immutable publication certainty captured at failure.
36    state: WriteFailureState,
37    /// Bytes acknowledged before the failure, independent of later recovery.
38    written_bytes: u64,
39    /// Opened writer retained for explicit recovery when available.
40    writer: Option<WriterRecovery>,
41}
42
43impl WriteAllFailure {
44    /// Builds a failure within the facade after a write or commit error.
45    pub(crate) fn new(
46        error: FsError,
47        state: WriteFailureState,
48        written_bytes: u64,
49        writer: Option<WriterRecovery>,
50    ) -> Self {
51        Self {
52            error: Box::new(error),
53            state,
54            written_bytes,
55            writer,
56        }
57    }
58    /// Returns the causal filesystem error.
59    #[inline]
60    #[must_use]
61    pub const fn error(&self) -> &FsError {
62        &self.error
63    }
64    /// Returns publication certainty at the original failure, even after
65    /// recovery.
66    #[must_use]
67    pub const fn state(&self) -> WriteFailureState {
68        self.state
69    }
70    /// Returns the original acknowledged byte count, excluding uncertain
71    /// writes.
72    #[must_use]
73    pub const fn written_bytes(&self) -> u64 {
74        self.written_bytes
75    }
76    /// Transfers the retained session without changing the failure snapshot.
77    #[must_use]
78    pub fn take_recovery(&mut self) -> Option<WriterRecovery> {
79        self.writer.take()
80    }
81    /// Returns the retained writer, if opening had completed.
82    #[inline]
83    #[must_use]
84    pub fn recovery(&self) -> Option<&WriterRecovery> {
85        self.writer.as_ref()
86    }
87    /// Returns a mutable retained writer for explicit recovery.
88    #[inline]
89    #[must_use]
90    pub fn recovery_mut(&mut self) -> Option<&mut WriterRecovery> {
91        self.writer.as_mut()
92    }
93    /// Returns the causal error and optional writer.
94    ///
95    /// # Returns
96    /// The original error, publication state, confirmed byte count, and
97    /// optional retained writer.
98    #[inline]
99    #[must_use]
100    pub fn into_parts(self) -> (FsError, WriteFailureState, u64, Option<WriterRecovery>) {
101        (*self.error, self.state, self.written_bytes, self.writer)
102    }
103}
104
105impl Display for WriteAllFailure {
106    /// Formats the causal failure without exposing writer internals.
107    #[inline]
108    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
109        self.error.fmt(formatter)
110    }
111}
112
113impl std::fmt::Debug for WriteAllFailure {
114    /// Formats the causal error and whether recovery is available.
115    #[inline]
116    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
117        formatter
118            .debug_struct("WriteAllFailure")
119            .field("error", &self.error)
120            .field("state", &self.state)
121            .field("written_bytes", &self.written_bytes)
122            .field("has_recovery", &self.writer.is_some())
123            .finish()
124    }
125}
126
127impl Error for WriteAllFailure {
128    /// Returns the underlying filesystem error.
129    #[inline]
130    fn source(&self) -> Option<&(dyn Error + 'static)> {
131        Some(self.error.as_ref())
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use std::hint::black_box;
138
139    use super::WriteAllFailure;
140    use crate::error::FsError;
141    use crate::error::FsErrorKind;
142    use crate::error::FsOperation;
143    use crate::write::WriteFailureState;
144
145    #[test]
146    fn failure_accessors_are_executed_at_runtime() {
147        let error: fn(&WriteAllFailure) -> &FsError = black_box(WriteAllFailure::error);
148        let recovery: fn(&WriteAllFailure) -> Option<&crate::write::WriterRecovery> =
149            black_box(WriteAllFailure::recovery);
150        let failure = WriteAllFailure::new(
151            FsError::new(FsErrorKind::NotFound, FsOperation::OpenWriter, "missing target"),
152            WriteFailureState::NotPublished,
153            4,
154            None,
155        );
156
157        assert_eq!(FsErrorKind::NotFound, error(&failure).kind());
158        assert!(recovery(&failure).is_none());
159    }
160}