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