Skip to main content

qubit_fs/temp/
rejected_temp_resource.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5// =============================================================================
6//! Explicit cleanup authority for a rejected temp session.
7
8use std::fmt::Debug;
9use std::fmt::Formatter;
10use std::fmt::Result as FmtResult;
11
12use crate::error::FsError;
13use crate::error::FsErrorKind;
14use crate::error::FsOperation;
15use crate::error::FsResult;
16use crate::error::RecoveryCleanupState;
17use crate::facade::internal::RecoveryCleanupGuard;
18use crate::path::Path;
19use crate::spi::TempResourceSpi;
20
21/// Isolated cleanup ownership after a provider returned an invalid identity.
22///
23/// This handle cannot write or publish. Cleanup acts only on resources owned by
24/// its session, never on a diagnostic path. Drop performs no explicit cleanup
25/// or cancellation hook; retain this handle until cleanup is confirmed.
26///
27/// ```compile_fail
28/// use qubit_fs::temp::RejectedTempResource;
29/// fn cannot_publish(mut recovery: RejectedTempResource) {
30///     recovery.keep();
31/// }
32/// ```
33///
34/// # Examples
35///
36/// ```rust
37/// use qubit_fs::error::RecoveryCleanupState;
38/// use qubit_fs::temp::RejectedTempResource;
39///
40/// assert!(std::any::type_name::<RejectedTempResource>().contains("RejectedTempResource"));
41/// assert_eq!(RecoveryCleanupState::Pending, RecoveryCleanupState::Pending);
42/// ```
43#[must_use = "explicitly clean or retain the isolated recovery session"]
44pub struct RejectedTempResource {
45    /// The actual provider session, retained independently of cleanup futures.
46    session: Box<dyn TempResourceSpi>,
47    /// Last observed cleanup state, never a publication snapshot.
48    state: RecoveryCleanupState,
49    /// Trusted configured provider identifier.
50    provider: Box<str>,
51    /// Requested path, never the unvalidated provider identity.
52    path: Option<Path>,
53}
54impl RejectedTempResource {
55    /// Takes ownership of a rejected session and trusted request context.
56    pub(crate) fn new(session: Box<dyn TempResourceSpi>, provider: &str, path: Option<Path>) -> Self {
57        Self {
58            session,
59            state: RecoveryCleanupState::Pending,
60            provider: provider.into(),
61            path,
62        }
63    }
64    /// Returns cleanup progress; errors and cancellation do not release
65    /// ownership.
66    pub const fn cleanup_state(&self) -> RecoveryCleanupState {
67        self.state
68    }
69
70    /// Explicitly cleans resources actually owned by this isolated session.
71    ///
72    /// A failed attempt retains the session for explicit retry or
73    /// reconciliation. This synchronous method may block in provider I/O.
74    ///
75    /// # Errors
76    /// Returns InvalidState after confirmed cleanup, or the contextual provider
77    /// error without replacing the original opening failure.
78    pub fn cleanup(&mut self) -> FsResult<()> {
79        if self.state == RecoveryCleanupState::Completed {
80            return Err(self.contextual_error(FsError::new(
81                FsErrorKind::InvalidState,
82                FsOperation::CleanupTemp,
83                "isolated session cleanup already completed",
84            )));
85        }
86        let mut guard = RecoveryCleanupGuard::start(&mut self.state);
87        let result = self.session.cleanup();
88        guard.finish(result.is_ok());
89        drop(guard);
90        result.map_err(|error| self.contextual_error(error))
91    }
92    /// Adds only trusted request context to a cleanup error.
93    fn contextual_error(&self, error: FsError) -> FsError {
94        error.with_trusted_cleanup_context(FsOperation::CleanupTemp, self.path.as_ref(), &self.provider)
95    }
96}
97impl Debug for RejectedTempResource {
98    /// Omits the session and unvalidated provider identity.
99    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
100        f.debug_struct("RejectedTempResource")
101            .field("cleanup_state", &self.state)
102            .finish_non_exhaustive()
103    }
104}