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.
// =============================================================================
// facade.

//! Immutable filesystem property snapshots used by facades.

use crate::error::FsError;
use crate::error::FsErrorKind;
use crate::error::FsOperation;
use crate::error::FsResult;
use crate::metadata::FileSystemCapabilities;
use crate::metadata::FileSystemCapability;
use crate::metadata::FileSystemInfo;
use crate::metadata::FileSystemLimits;
use crate::metadata::SymlinkPolicy;
use crate::path::Path;
use crate::path::PathConstraints;
use crate::path::PathForm;
use crate::path::PathSemantics;
use crate::spi::ProviderOperation;
use crate::spi::ProviderProperties;

/// Immutable construction-time properties cached by a filesystem facade.
///
/// A facade exposes the provider's validated identity, limits, path rules, and
/// effective capabilities through one stable snapshot. The capability set may
/// include facts derived by the facade; it is therefore not necessarily a
/// byte-for-byte copy of the provider declaration.
///
/// # Examples
///
/// ```
/// use qubit_fs::metadata::{FileSystemCapabilities, FileSystemId, FileSystemInfo,
///     FileSystemLimit, FileSystemLimits, SymlinkPolicy};
/// use qubit_fs::path::{PathConstraints, PathForm, PathSemantics};
/// use qubit_fs::metadata::FileSystemProperties;
///
/// let properties = FileSystemProperties::new(
///     FileSystemInfo::new(FileSystemId::new("example")?, "example", PathSemantics::Hierarchical),
///     FileSystemCapabilities::new(),
///     FileSystemLimits::unknown().with_max_path_text_bytes(FileSystemLimit::Maximum(4096)),
///     PathConstraints::absolute(),
///     SymlinkPolicy::Reject,
/// )?;
/// assert_eq!(properties.info().provider_id(), "example");
/// assert_eq!(properties.limits().max_path_text_bytes(), FileSystemLimit::Maximum(4096));
/// assert_eq!(properties.path_constraints().form(), PathForm::Absolute);
/// # Ok::<(), qubit_fs::FsError>(())
/// ```
#[derive(Clone, Debug)]
pub struct FileSystemProperties {
    /// Stable filesystem information.
    info: FileSystemInfo,
    /// Stable advertised capabilities.
    capabilities: FileSystemCapabilities,
    /// Stable provider limits.
    limits: FileSystemLimits,
    /// Accepted logical path forms.
    path_constraints: PathConstraints,
    /// Provider-declared symbolic-link traversal policy.
    symlink_policy: SymlinkPolicy,
}

impl FileSystemProperties {
    /// Derives the application-visible property snapshot from one validated
    /// provider snapshot.
    ///
    /// The facade adds conditional copy support only when the provider exposes
    /// metadata, reader, and writer entry points together with corresponding
    /// read and write capabilities. This method performs no I/O.
    ///
    /// # Parameters
    /// - `provider`: Validated provider operations, guarantees, and limits.
    ///
    /// # Returns
    /// A validated application-visible property snapshot.
    ///
    /// # Errors
    /// Returns an invalid-options error when the derived snapshot violates a
    /// shared property invariant.
    pub(crate) fn from_provider(provider: &ProviderProperties) -> FsResult<Self> {
        let mut capabilities = provider.declared_capabilities();
        let operations = provider.operations();
        let streamed_copy = operations.supports(ProviderOperation::Stat)
            && operations.supports(ProviderOperation::OpenReader)
            && operations.supports(ProviderOperation::OpenWriter)
            && capabilities.supports(FileSystemCapability::Read)
            && capabilities.supports(FileSystemCapability::Write);
        if streamed_copy && !capabilities.supports(FileSystemCapability::Copy) {
            capabilities = capabilities.with_conditional(FileSystemCapability::Copy);
        }
        Self::new(
            provider.info().clone(),
            capabilities,
            *provider.limits(),
            provider.path_constraints().clone(),
            provider.symlink_policy(),
        )
    }

    /// Builds and validates an immutable filesystem property snapshot.
    ///
    /// This method performs no I/O.
    ///
    /// # Parameters
    /// - `info`: Stable provider identity and path semantics.
    /// - `capabilities`: Capabilities explicitly advertised by the provider.
    /// - `limits`: Provider resource and operation limits.
    /// - `path_constraints`: Accepted absolute and relative path forms.
    /// - `symlink_policy`: Provider-declared symbolic-link traversal policy.
    ///
    /// # Returns
    /// A validated immutable property snapshot.
    ///
    /// # Errors
    /// Returns an invalid-options error when the provider identity is invalid,
    /// advertised capabilities violate dependencies, or path configuration is
    /// internally inconsistent.
    #[inline]
    pub fn new(
        info: FileSystemInfo,
        capabilities: FileSystemCapabilities,
        limits: FileSystemLimits,
        path_constraints: PathConstraints,
        symlink_policy: SymlinkPolicy,
    ) -> FsResult<Self> {
        let properties = Self {
            info,
            capabilities,
            limits,
            path_constraints,
            symlink_policy,
        };
        properties.validate()?;
        Ok(properties)
    }

    /// Returns the stable filesystem identity and configuration.
    ///
    /// # Returns
    /// The immutable provider information snapshot.
    #[inline]
    #[must_use]
    pub const fn info(&self) -> &FileSystemInfo {
        &self.info
    }

    /// Returns the effective application-visible capabilities.
    ///
    /// The snapshot contains provider-declared capabilities plus capabilities
    /// derived by the facade, such as conditional streamed copy support.
    ///
    /// # Returns
    /// Capabilities available to callers of the facade.
    #[inline]
    #[must_use]
    pub const fn capabilities(&self) -> FileSystemCapabilities {
        self.capabilities
    }

    /// Returns the stable filesystem limits.
    ///
    /// # Returns
    /// The immutable provider limit snapshot.
    #[inline]
    #[must_use]
    pub const fn limits(&self) -> &FileSystemLimits {
        &self.limits
    }

    /// Returns the immutable accepted path constraints.
    ///
    /// # Returns
    /// The accepted logical path forms.
    #[inline]
    #[must_use]
    pub const fn path_constraints(&self) -> &PathConstraints {
        &self.path_constraints
    }

    /// Returns the provider-declared symbolic-link traversal policy.
    #[inline]
    #[must_use = "the filesystem symbolic-link policy must be used"]
    pub const fn symlink_policy(&self) -> SymlinkPolicy {
        self.symlink_policy
    }

    /// Validates a logical path against the provider's semantics, form, and
    /// byte limits without performing I/O.
    ///
    /// # Errors
    /// Returns an enriched invalid-path or resource-limit error when the path
    /// does not satisfy this filesystem's declared contract.
    pub fn validate_path(&self, path: &Path, operation: FsOperation) -> FsResult<()> {
        let result = if path.semantics() != self.info.path_semantics() {
            Err(FsError::invalid_path(
                operation,
                "path semantics do not match this filesystem",
            ))
        } else {
            self.path_constraints
                .validate(path)
                .and_then(|()| self.limits.validate_path(path, self.info.path_semantics(), operation))
        };
        result.map_err(|error| {
            error
                .with_operation(operation)
                .with_missing_context(path, None, self.info.provider_id())
        })
    }

    /// Defensively validates a provider-supplied snapshot at the facade
    /// boundary.
    ///
    /// It performs no I/O and is intentionally crate-private.
    ///
    /// # Returns
    /// `Ok(())` when all property invariants hold.
    ///
    /// # Errors
    /// Returns an invalid-options error when the snapshot violates core value
    /// invariants.
    pub(crate) fn validate(&self) -> FsResult<()> {
        if self.info.provider_id().is_empty() || self.info.provider_id().chars().any(char::is_control) {
            return Err(invalid_properties(
                "provider id must be non-empty and contain no controls",
            ));
        }
        if let Some((_capability, _dependency)) = self.capabilities.missing_dependency() {
            return Err(invalid_properties("advertised capability dependency is missing"));
        }
        if [
            self.limits.max_path_text_bytes(),
            self.limits.max_component_text_bytes(),
            self.limits.max_read_range_bytes(),
            self.limits.max_write_bytes(),
            self.limits.max_list_page_entries(),
        ]
        .into_iter()
        .any(|limit| matches!(limit, crate::metadata::FileSystemLimit::Maximum(0)))
        {
            return Err(invalid_properties(
                "finite filesystem limits must have a positive value",
            ));
        }
        if self.info.path_semantics() != PathSemantics::Hierarchical
            && self.path_constraints.form() == PathForm::Absolute
        {
            return Err(invalid_properties(
                "literal path semantics cannot require hierarchical absolute paths",
            ));
        }
        Ok(())
    }
}

/// Builds the shared property-validation failure.
///
/// # Parameters
/// - `message`: Static explanation of the violated property invariant.
///
/// # Returns
/// An invalid-options error scoped to provider configuration.
fn invalid_properties(message: &'static str) -> FsError {
    FsError::new(FsErrorKind::InvalidOptions, FsOperation::ValidateProperties, message)
}