qubit-fs 0.2.2

Provider-neutral synchronous and asynchronous filesystem abstraction for Rust
Documentation
// =============================================================================
//    Copyright (c) 2025 - 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================

//! Regression coverage for read-all byte budgets and range windows.

use std::io::Cursor;
use std::sync::Arc;

use qubit_fs::FileSystem;
use qubit_fs::FsResult;
use qubit_fs::Path;
use qubit_fs::directory::CreateDirectoryOutcome;
use qubit_fs::directory::DeleteOutcome;
use qubit_fs::error::FsError;
use qubit_fs::error::FsErrorKind;
use qubit_fs::error::FsOperation;
use qubit_fs::metadata::FileKind;
use qubit_fs::metadata::FileMetadata;
use qubit_fs::metadata::FileSystemCapabilities;
use qubit_fs::metadata::FileSystemCapability;
use qubit_fs::metadata::FileSystemId;
use qubit_fs::metadata::FileSystemInfo;
use qubit_fs::metadata::FileSystemLimits;
use qubit_fs::metadata::OpenedFileInfo;
use qubit_fs::metadata::SymlinkPolicy;
use qubit_fs::path::PathConstraints;
use qubit_fs::path::PathSemantics;
use qubit_fs::read::ReadOptions;
use qubit_fs::rename::RenameFailureState;
use qubit_fs::rename::RenameOutcome;
use qubit_fs::spi::CreateDirectoryRequest;
use qubit_fs::spi::CreateTempDirectoryRequest;
use qubit_fs::spi::CreateTempFileRequest;
use qubit_fs::spi::DeleteDirectoryRequest;
use qubit_fs::spi::DeleteFileRequest;
use qubit_fs::spi::FileSystemSpi;
use qubit_fs::spi::ListRequest;
use qubit_fs::spi::OpenReaderRequest;
use qubit_fs::spi::OpenWriterRequest;
use qubit_fs::spi::OpenedDirectoryStream;
use qubit_fs::spi::OpenedReader;
use qubit_fs::spi::OpenedTempDirectory;
use qubit_fs::spi::OpenedTempFile;
use qubit_fs::spi::OpenedWriter;
use qubit_fs::spi::ProviderOperation;
use qubit_fs::spi::ProviderOperations;
use qubit_fs::spi::ProviderProperties;
use qubit_fs::spi::RenameRequest;
use qubit_fs::spi::SpiRenameFailure;
use qubit_fs::spi::StatRequest;
use qubit_fs::spi::StatResponse;

struct RangeSpi {
    metadata: bool,
    fail_open: bool,
}

impl RangeSpi {
    fn with_metadata() -> Self {
        Self {
            metadata: true,
            fail_open: false,
        }
    }

    fn without_metadata() -> Self {
        Self {
            metadata: false,
            fail_open: false,
        }
    }
}

impl FileSystemSpi for RangeSpi {
    fn properties(&self) -> ProviderProperties {
        ProviderProperties::new(
            FileSystemInfo::new(
                FileSystemId::new("read-window").unwrap(),
                "read-window",
                PathSemantics::Hierarchical,
            ),
            ProviderOperations::new().with(ProviderOperation::OpenReader),
            FileSystemCapabilities::new()
                .with_guaranteed(FileSystemCapability::Read)
                .with_guaranteed(FileSystemCapability::RangeRead),
            FileSystemLimits::unknown(),
            PathConstraints::absolute(),
            SymlinkPolicy::Reject,
        )
        .unwrap()
    }
    fn stat(&self, _: StatRequest<'_>) -> FsResult<StatResponse> {
        Err(unused())
    }
    fn list(&self, _: ListRequest<'_>) -> FsResult<OpenedDirectoryStream> {
        Err(unused())
    }
    fn open_reader(&self, request: OpenReaderRequest<'_>) -> FsResult<OpenedReader> {
        if self.fail_open {
            return Err(FsError::new(FsErrorKind::Io, FsOperation::OpenReader, "open failed"));
        }
        let options = request.options().options();
        let total = b"0123456789";
        let start = options.offset().unwrap_or(0).min(total.len() as u64) as usize;
        let end = options
            .length()
            .map_or(total.len(), |n| start.saturating_add(n as usize).min(total.len()));
        let mut info = OpenedFileInfo::new(FileSystemId::new("read-window").unwrap(), request.path().clone());
        if self.metadata {
            info = info.with_metadata(FileMetadata::new(FileKind::File).with_len(Some(total.len() as u64)));
        }
        Ok(OpenedReader::new(
            info,
            Box::new(Cursor::new(total[start..end].to_vec())),
        ))
    }
    fn open_writer(&self, _: OpenWriterRequest<'_>) -> FsResult<OpenedWriter> {
        Err(unused())
    }
    fn create_directory(&self, _: CreateDirectoryRequest<'_>) -> FsResult<CreateDirectoryOutcome> {
        Err(unused())
    }
    fn delete_file(&self, _: DeleteFileRequest<'_>) -> FsResult<DeleteOutcome> {
        Err(unused())
    }
    fn delete_directory(&self, _: DeleteDirectoryRequest<'_>) -> FsResult<DeleteOutcome> {
        Err(unused())
    }
    fn rename(&self, _: RenameRequest<'_>) -> Result<RenameOutcome, SpiRenameFailure> {
        Err(SpiRenameFailure::new(unused(), RenameFailureState::Unchanged))
    }
    fn create_temp_file(&self, _: CreateTempFileRequest) -> FsResult<OpenedTempFile> {
        Err(unused())
    }
    fn create_temp_directory(&self, _: CreateTempDirectoryRequest) -> FsResult<OpenedTempDirectory> {
        Err(unused())
    }
}

fn unused() -> FsError {
    FsError::new(FsErrorKind::UnsupportedOperation, FsOperation::Other, "unused")
}

#[test]
fn metadata_length_is_compared_with_selected_window() {
    let fs = FileSystem::from_spi(RangeSpi::with_metadata()).unwrap();
    let path = Path::parse("/source").unwrap();
    let options = ReadOptions::default().with_offset(Some(2)).with_length(Some(3));
    assert_eq!(b"234", fs.read_all(&path, options.clone(), 3).unwrap().as_slice());
    assert_eq!(
        FsErrorKind::ResourceLimitExceeded,
        fs.read_all(&path, options, 2).unwrap_err().kind()
    );
}

#[test]
fn metadata_length_does_not_reject_empty_range_at_eof() {
    let fs = FileSystem::from_shared_spi(Arc::new(RangeSpi::with_metadata())).unwrap();
    let path = Path::parse("/source").unwrap();
    let options = ReadOptions::default().with_offset(Some(100));
    assert!(fs.read_all(&path, options, 0).unwrap().is_empty());
}

#[test]
fn read_all_accepts_unknown_metadata_but_still_enforces_stream_budget() {
    let fs = FileSystem::from_spi(RangeSpi::without_metadata()).unwrap();
    let path = Path::parse("/source").unwrap();
    let options = ReadOptions::default().with_offset(Some(2)).with_length(Some(3));
    assert_eq!(b"234", fs.read_all(&path, options.clone(), 3).unwrap().as_slice());
    assert_eq!(
        FsErrorKind::ResourceLimitExceeded,
        fs.read_all(&path, options, 2).unwrap_err().kind()
    );
}

#[test]
fn read_all_reports_open_error_before_budget_collection() {
    let fs = FileSystem::from_spi(RangeSpi {
        metadata: true,
        fail_open: true,
    })
    .unwrap();
    let error = fs
        .read_all(&Path::parse("/source").unwrap(), Default::default(), 16)
        .unwrap_err();
    assert_eq!(FsErrorKind::Io, error.kind());
}