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.
// =============================================================================
//! Recovery-state mapping shared by synchronous and asynchronous copy fallback.
// The facade-level mappings are covered by copy_fallback_tests.rs and
// async_copy_fallback_tests.rs.

use crate::copy::CopyFailureState;
use crate::copy::CopyStats;
use crate::write::WriteFailureState;
use crate::write::WriterState;

/// Maps an opened writer lifecycle state to a copy recovery state.
///
/// The writer SPI publishes accepted bytes only at commit. An open,
/// not-published, or aborted writer therefore has not published a destination
/// effect.
///
/// # Parameters
///
/// - `state`: Current lifecycle state of the fallback destination writer.
///
/// # Returns
///
/// The copy recovery state implied by the writer publication certainty.
#[inline]
pub(crate) const fn from_writer_state(state: WriterState) -> CopyFailureState {
    match state {
        WriterState::Open | WriterState::NotPublished | WriterState::Aborted => CopyFailureState::Unchanged,
        WriterState::Committed | WriterState::Published => CopyFailureState::Published,
        WriterState::Indeterminate => CopyFailureState::Indeterminate,
    }
}

/// Maps a provider-confirmed write-commit failure to a copy recovery state.
///
/// # Parameters
///
/// - `state`: Publication certainty reported by the provider commit attempt.
///
/// # Returns
///
/// The equivalent recovery state for the enclosing copy operation.
#[inline]
pub(crate) const fn from_write_failure_state(state: WriteFailureState) -> CopyFailureState {
    match state {
        WriteFailureState::RetryableNotPublished | WriteFailureState::NotPublished => CopyFailureState::Unchanged,
        WriteFailureState::Published => CopyFailureState::Published,
        WriteFailureState::Indeterminate => CopyFailureState::Indeterminate,
    }
}

/// Maps validated native success statistics to publication certainty after a
/// later cooperative deadline check fails.
#[inline]
pub(crate) const fn from_completed_stats(stats: &CopyStats) -> CopyFailureState {
    if stats.files != 0
        || stats.directories != 0
        || stats.symlinks != 0
        || stats.objects != 0
        || stats.prefixes != 0
        || stats.overwritten != 0
    {
        if stats.failed == 0 {
            CopyFailureState::Published
        } else {
            CopyFailureState::PartiallyPublished
        }
    } else if stats.skipped != 0 && stats.failed == 0 && stats.bytes == 0 {
        CopyFailureState::Unchanged
    } else {
        CopyFailureState::Indeterminate
    }
}

/// Builds failure statistics after a fallback destination writer was opened.
///
/// # Parameters
///
/// - `bytes`: Bytes accepted by the destination writer before the failure.
///
/// # Returns
///
/// Failure statistics retaining the observed byte count.
#[inline]
pub(crate) const fn fallback_failure_stats(bytes: u64) -> CopyStats {
    CopyStats {
        files: 0,
        directories: 0,
        symlinks: 0,
        objects: 0,
        prefixes: 0,
        bytes,
        overwritten: 0,
        skipped: 0,
        failed: 1,
    }
}

#[cfg(test)]
mod tests {
    use std::hint::black_box;

    use super::from_completed_stats;
    use crate::copy::CopyFailureState;
    use crate::copy::CopyStats;

    #[test]
    fn completed_statistics_map_to_publication_states() {
        let mapper: fn(&CopyStats) -> CopyFailureState = black_box(from_completed_stats);

        assert_eq!(
            CopyFailureState::Published,
            mapper(&CopyStats {
                files: 1,
                ..CopyStats::default()
            })
        );
        assert_eq!(
            CopyFailureState::PartiallyPublished,
            mapper(&CopyStats {
                files: 1,
                failed: 1,
                ..CopyStats::default()
            }),
        );
        assert_eq!(
            CopyFailureState::Unchanged,
            mapper(&CopyStats {
                skipped: 1,
                ..CopyStats::default()
            }),
        );
        assert_eq!(CopyFailureState::Indeterminate, mapper(&CopyStats::default()));
    }
}