Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/filesystem/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ readme = "README.md"
async-trait.workspace = true
log.workspace = true
snafu.workspace = true
tokio = { workspace = true, features = ["fs", "sync", "io-util"] }
tokio = { workspace = true, features = ["fs", "sync", "io-util", "time"] }

[dev-dependencies]
tempfile.workspace = true
Expand Down
47 changes: 47 additions & 0 deletions crates/filesystem/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! # Configuration Module
//!
//! Provides configuration types for filesystem operations, including timeout settings.
//!
//! ## What
//!
//! This module defines [`FileSystemConfig`] and its builder, which control the behavior
//! of filesystem operations. The primary configuration options are timeout values for
//! different operation categories.
//!
//! ## How
//!
//! Configuration is created using the builder pattern:
//! 1. Call `FileSystemConfig::builder()` to get a `FileSystemConfigBuilder`
//! 2. Chain configuration methods (e.g., `with_read_timeout()`)
//! 3. Call `build()` to obtain the final `FileSystemConfig`
//!
//! The configuration is immutable once built, ensuring thread-safety and preventing
//! accidental modification during filesystem operations.
//!
//! ## Why
//!
//! Configurable timeouts are essential for:
//! - **Reliability**: Prevent operations from hanging on unresponsive filesystems
//! - **Flexibility**: Different use cases require different timeout values
//! - **Testability**: Mock filesystems can use shorter timeouts for faster tests
//! - **Predictability**: Known timeout behavior helps with error handling
//!
//! ## Example
//!
//! ```rust,ignore
//! use workspace_fs::FileSystemConfig;
//! use std::time::Duration;
//!
//! // Use default configuration (30s read/write, 60s operation)
//! let config = FileSystemConfig::default();
//!
//! // Custom configuration with builder
//! let config = FileSystemConfig::builder()
//! .with_read_timeout(Duration::from_secs(10))
//! .with_write_timeout(Duration::from_secs(10))
//! .with_operation_timeout(Duration::from_secs(30))
//! .build();
//! ```

// TODO: will be implemented on epic workspace-node-tools-g2t (Configuration Module)
#![allow(clippy::todo)]
42 changes: 42 additions & 0 deletions crates/filesystem/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! # Error Module
//!
//! Defines the unified error type for all filesystem operations in workspace-fs.
//!
//! ## What
//!
//! This module provides a single [`Error`] enum that captures all possible error
//! conditions that can occur during filesystem operations. It uses the `snafu` crate
//! for ergonomic error handling with context.
//!
//! ## How
//!
//! The error type is built using `snafu`'s derive macro, which automatically generates:
//! - `Error` and `Display` implementations
//! - Context selectors for each variant
//! - Backtrace capture (when enabled)
//!
//! Each variant includes the path that caused the error and, where applicable, the
//! underlying system error. This provides rich debugging information.
//!
//! ## Why
//!
//! A unified error type ensures:
//! - **Consistency**: All filesystem operations return the same error type
//! - **Context Preservation**: Every error includes the path and operation that failed
//! - **Type Safety**: Compile-time guarantees about error handling
//! - **Ergonomics**: Easy error conversion with the `?` operator
//!
//! ## Example
//!
//! ```rust,ignore
//! use workspace_fs::{Error, Result};
//! use std::path::Path;
//!
//! async fn read_config(path: &Path) -> Result<String> {
//! // Errors automatically include the path that failed
//! fs.read_to_string(path).await
//! }
//! ```

// TODO: will be implemented on epic workspace-node-tools-906 (Error Module)
#![allow(clippy::todo)]
73 changes: 73 additions & 0 deletions crates/filesystem/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,84 @@
//! }
//! ```

// =============================================================================
// Safety: This crate contains no unsafe code (NFR-4.1)
// =============================================================================
#![forbid(unsafe_code)]
// =============================================================================
// Documentation lints
// =============================================================================
#![warn(missing_docs)]
#![warn(rustdoc::missing_crate_level_docs)]
// =============================================================================
// Code quality lints (deny unwrap/expect/todo/unimplemented/panic in production)
// =============================================================================
#![deny(unused_must_use)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::todo)]
#![deny(clippy::unimplemented)]
#![deny(clippy::panic)]

// =============================================================================
// Module Declarations
// =============================================================================

/// Error types for filesystem operations.
///
/// Provides a unified [`Error`](error) enum that captures all possible error
/// conditions with path context for debugging.
pub(crate) mod error;

/// Configuration types for filesystem behavior.
///
/// Provides [`FileSystemConfig`](config) with timeout settings and a builder
/// pattern for customization.
pub(crate) mod config;

/// Core data types for filesystem entries.
///
/// Provides [`FileType`](types), [`DirEntry`](types), and [`Metadata`](types)
/// types used throughout the crate.
pub(crate) mod types;

/// The core filesystem trait definition.
///
/// Defines the [`FileSystem`](traits) trait that abstracts all filesystem
/// operations for dependency injection and testing.
pub(crate) mod traits;

/// Path utility extensions.
///
/// Provides the [`PathExt`](path_ext) trait with synchronous path manipulation
/// utilities that don't perform I/O.
pub(crate) mod path_ext;

/// Real filesystem implementation using tokio::fs.
///
/// Provides [`RealFileSystem`](real) for production use with async I/O
/// and configurable timeout support.
pub(crate) mod real;

/// Mock filesystem implementation for testing.
///
/// Provides [`MockFileSystem`](mock) for fast, deterministic unit tests
/// without touching the disk.
pub(crate) mod mock;

/// Unit tests organized by module.
#[cfg(test)]
mod tests;

// =============================================================================
// Public Re-exports
// =============================================================================

// TODO: Re-exports will be added as modules are implemented
// pub use config::{FileSystemConfig, FileSystemConfigBuilder};
// pub use error::{Error, Result};
// pub use mock::MockFileSystem;
// pub use path_ext::PathExt;
// pub use real::RealFileSystem;
// pub use traits::FileSystem;
// pub use types::{DirEntry, FileType, Metadata};
67 changes: 67 additions & 0 deletions crates/filesystem/src/mock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! # Mock FileSystem Module
//!
//! In-memory implementation of the [`FileSystem`](crate::FileSystem) trait for testing.
//!
//! ## What
//!
//! This module provides [`MockFileSystem`], an in-memory filesystem implementation
//! designed for unit and integration testing. It simulates a complete filesystem
//! without touching the disk, enabling fast, deterministic, and isolated tests.
//!
//! ## How
//!
//! `MockFileSystem` maintains an in-memory data structure (typically a `HashMap` or
//! tree structure) that represents the filesystem state:
//! - Files are stored as byte vectors with associated metadata
//! - Directories are represented as containers of entries
//! - Symlinks store their target paths
//!
//! The implementation supports:
//! - Pre-populating the filesystem with test data
//! - Simulating errors for specific paths (e.g., permission denied)
//! - Inspecting filesystem state after operations
//!
//! Unlike `RealFileSystem`, the mock does not enforce timeouts, as all operations
//! are instantaneous in-memory operations.
//!
//! ## Why
//!
//! A mock filesystem is essential for:
//! - **Speed**: No disk I/O means tests run in microseconds, not milliseconds
//! - **Determinism**: Tests produce the same results regardless of host filesystem state
//! - **Isolation**: Tests cannot interfere with each other or the host system
//! - **Error Simulation**: Can test error handling without complex setup
//! - **CI/CD Compatibility**: Works in containerized environments with read-only filesystems
//!
//! ## Example
//!
//! ```rust,ignore
//! use workspace_fs::{FileSystem, MockFileSystem};
//! use std::path::Path;
//!
//! #[tokio::test]
//! async fn test_read_config() {
//! // Create mock filesystem with test data
//! let mock = MockFileSystem::new();
//! mock.create_file(
//! Path::new("config.json"),
//! r#"{"name": "test-project"}"#,
//! ).await.unwrap();
//!
//! // Test the function under test
//! let content = mock.read_to_string(Path::new("config.json")).await.unwrap();
//! assert!(content.contains("test-project"));
//! }
//!
//! #[tokio::test]
//! async fn test_file_not_found() {
//! let mock = MockFileSystem::new();
//!
//! // Test error handling
//! let result = mock.read_to_string(Path::new("nonexistent.json")).await;
//! assert!(result.is_err());
//! }
//! ```

// TODO: will be implemented on epic workspace-node-tools-0ea (MockFileSystem Implementation)
#![allow(clippy::todo)]
43 changes: 43 additions & 0 deletions crates/filesystem/src/path_ext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//! # Path Extension Module
//!
//! Provides utility extensions for working with filesystem paths.
//!
//! ## What
//!
//! This module defines the [`PathExt`] trait, which adds convenience methods to
//! [`std::path::Path`] for common path manipulation tasks. These are pure functions
//! that operate on path data without performing any I/O operations.
//!
//! ## How
//!
//! The trait is implemented for `Path` and provides methods such as:
//! - Path normalization (resolving `.` and `..` components)
//! - Cross-platform path handling
//! - Extension manipulation utilities
//!
//! All methods are synchronous because they operate only on in-memory path data,
//! not on the filesystem. This makes them safe to use in any context.
//!
//! ## Why
//!
//! Path extension utilities are valuable for:
//! - **Normalization**: Ensure consistent path representation across platforms
//! - **Safety**: Validate paths before filesystem operations
//! - **Convenience**: Common operations available as methods, not functions
//! - **Portability**: Abstract over platform-specific path separators
//!
//! ## Example
//!
//! ```rust,ignore
//! use workspace_fs::PathExt;
//! use std::path::Path;
//!
//! let path = Path::new("/foo/bar/../baz/./qux");
//!
//! // Normalize path without touching filesystem
//! let normalized = path.normalize();
//! assert_eq!(normalized, Path::new("/foo/baz/qux"));
//! ```

// TODO: will be implemented on epic workspace-node-tools-60y (PathExt Module)
#![allow(clippy::todo)]
58 changes: 58 additions & 0 deletions crates/filesystem/src/real.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! # Real FileSystem Module
//!
//! Production implementation of the [`FileSystem`](crate::FileSystem) trait using `tokio::fs`.
//!
//! ## What
//!
//! This module provides [`RealFileSystem`], the production-ready filesystem implementation
//! that performs actual I/O operations on the host's filesystem. It wraps `tokio::fs` to
//! provide async filesystem access with configurable timeout support.
//!
//! ## How
//!
//! `RealFileSystem` implements the `FileSystem` trait by:
//! 1. Delegating each operation to the corresponding `tokio::fs` function
//! 2. Wrapping operations in `tokio::time::timeout` using configured durations
//! 3. Converting `std::io::Error` into the crate's `Error` type with path context
//! 4. Logging operations at appropriate levels (trace for entry, debug for results)
//!
//! The implementation is stateless (configuration is stored but not mutated), making it
//! safe to share across tasks via `Arc<RealFileSystem>`.
//!
//! ## Why
//!
//! A dedicated real filesystem implementation provides:
//! - **Async I/O**: Non-blocking operations for better concurrency in large monorepos
//! - **Timeouts**: Prevent hanging on slow or unresponsive filesystems (e.g., network mounts)
//! - **Error Context**: Every error includes the path and operation that failed
//! - **Logging**: Integrated logging for debugging and observability
//! - **Testability**: Can be swapped for `MockFileSystem` in tests
//!
//! ## Example
//!
//! ```rust,ignore
//! use workspace_fs::{FileSystem, RealFileSystem, FileSystemConfig};
//! use std::path::Path;
//! use std::time::Duration;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), workspace_fs::Error> {
//! // Default configuration with 30s read/write, 60s operation timeouts
//! let fs = RealFileSystem::new();
//!
//! // Custom configuration
//! let config = FileSystemConfig::builder()
//! .with_read_timeout(Duration::from_secs(10))
//! .build();
//! let fs = RealFileSystem::with_config(config);
//!
//! // Use the filesystem
//! let content = fs.read_to_string(Path::new("package.json")).await?;
//! println!("Content: {}", content);
//!
//! Ok(())
//! }
//! ```

// TODO: will be implemented on epic workspace-node-tools-1gx (RealFileSystem Implementation)
#![allow(clippy::todo)]
Loading