qubit-fs 0.2.2

Provider-neutral synchronous and asynchronous filesystem abstraction for Rust
Documentation
// =============================================================================
//    Copyright (c) 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================
//! Explicit cleanup authority for a rejected temp session.

use std::fmt::Debug;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;

use crate::error::FsError;
use crate::error::FsErrorKind;
use crate::error::FsOperation;
use crate::error::FsResult;
use crate::error::RecoveryCleanupState;
use crate::facade::internal::RecoveryCleanupGuard;
use crate::path::Path;
use crate::spi::TempResourceSpi;

/// Isolated cleanup ownership after a provider returned an invalid identity.
///
/// This handle cannot write or publish. Cleanup acts only on resources owned by
/// its session, never on a diagnostic path. Drop performs no explicit cleanup
/// or cancellation hook; retain this handle until cleanup is confirmed.
///
/// ```compile_fail
/// use qubit_fs::temp::RejectedTempResource;
/// fn cannot_publish(mut recovery: RejectedTempResource) {
///     recovery.keep();
/// }
/// ```
///
/// # Examples
///
/// ```rust
/// use qubit_fs::error::RecoveryCleanupState;
/// use qubit_fs::temp::RejectedTempResource;
///
/// assert!(std::any::type_name::<RejectedTempResource>().contains("RejectedTempResource"));
/// assert_eq!(RecoveryCleanupState::Pending, RecoveryCleanupState::Pending);
/// ```
#[must_use = "explicitly clean or retain the isolated recovery session"]
pub struct RejectedTempResource {
    /// The actual provider session, retained independently of cleanup futures.
    session: Box<dyn TempResourceSpi>,
    /// Last observed cleanup state, never a publication snapshot.
    state: RecoveryCleanupState,
    /// Trusted configured provider identifier.
    provider: Box<str>,
    /// Requested path, never the unvalidated provider identity.
    path: Option<Path>,
}
impl RejectedTempResource {
    /// Takes ownership of a rejected session and trusted request context.
    pub(crate) fn new(session: Box<dyn TempResourceSpi>, provider: &str, path: Option<Path>) -> Self {
        Self {
            session,
            state: RecoveryCleanupState::Pending,
            provider: provider.into(),
            path,
        }
    }
    /// Returns cleanup progress; errors and cancellation do not release
    /// ownership.
    pub const fn cleanup_state(&self) -> RecoveryCleanupState {
        self.state
    }

    /// Explicitly cleans resources actually owned by this isolated session.
    ///
    /// A failed attempt retains the session for explicit retry or
    /// reconciliation. This synchronous method may block in provider I/O.
    ///
    /// # Errors
    /// Returns InvalidState after confirmed cleanup, or the contextual provider
    /// error without replacing the original opening failure.
    pub fn cleanup(&mut self) -> FsResult<()> {
        if self.state == RecoveryCleanupState::Completed {
            return Err(self.contextual_error(FsError::new(
                FsErrorKind::InvalidState,
                FsOperation::CleanupTemp,
                "isolated session cleanup already completed",
            )));
        }
        let mut guard = RecoveryCleanupGuard::start(&mut self.state);
        let result = self.session.cleanup();
        guard.finish(result.is_ok());
        drop(guard);
        result.map_err(|error| self.contextual_error(error))
    }
    /// Adds only trusted request context to a cleanup error.
    fn contextual_error(&self, error: FsError) -> FsError {
        error.with_trusted_cleanup_context(FsOperation::CleanupTemp, self.path.as_ref(), &self.provider)
    }
}
impl Debug for RejectedTempResource {
    /// Omits the session and unvalidated provider identity.
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.debug_struct("RejectedTempResource")
            .field("cleanup_state", &self.state)
            .finish_non_exhaustive()
    }
}