workspace_tools
Stop fighting with file paths in Rust. workspace_tools provides foolproof, workspace-relative path resolution that works everywhere: in your tests, binaries, and examples, regardless of the execution context.
It's the missing piece of the Rust development workflow that lets you focus on building, not on debugging broken paths.
🎯 The Problem: Brittle File Paths
Every Rust developer has faced this. Your code works on your machine, but breaks in CI or when run from a different directory.
// ❌ Brittle: This breaks if you run `cargo test` or execute the binary from a subdirectory.
let config = read_to_string?;
// ❌ Inconsistent: This relies on the current working directory, which is unpredictable.
let data = new;
✅ The Solution: A Reliable Workspace Anchor
workspace_tools gives you a stable anchor to your project's root, making all file operations simple and predictable.
use workspace;
// ✅ Reliable: This works from anywhere.
let ws = workspace?; // Automatically finds your project root!
let config = read_to_string?;
let data = ws.data_dir.join; // Use standard, predictable directories.
🚀 Quick Start in 60 Seconds
Get up and running with a complete, working example in less than a minute.
1. Add the Dependency
In your project's root directory, run:
2. Use it in Your Code
workspace_tools automatically finds your project root by looking for the Cargo.toml file that contains your [workspace] definition. No configuration is required.
use workspace;
use fs;
use Path;
// Helper function to create a dummy config file for the example.
3. Run Your Application
Run your code from different directories to see workspace_tools in action:
# Run from the project root (this will work)
# Run from a subdirectory (this will also work!)
You have now eliminated brittle, context-dependent file paths from your project!
📁 A Standard for Project Structure
workspace_tools helps standardize your projects, making them instantly familiar to you, your team, and your tools.
your-project/
├── .cargo/
├── secret/ # (Optional) Securely manage secrets
├── .workspace/ # Internal workspace metadata
├── Cargo.toml # Your workspace root
├── config/ # ( ws.config_dir() ) Application configuration
├── data/ # ( ws.data_dir() ) Databases, caches, user data
├── docs/ # ( ws.docs_dir() ) Project documentation
├── logs/ # ( ws.logs_dir() ) Runtime log files
├── src/
└── tests/ # ( ws.tests_dir() ) Integration tests & fixtures
🔧 Optional Features
Enable additional functionality as needed in your Cargo.toml:
Serde Integration (serde) - enabled by default
Load .toml, .json, and .yaml files directly into structs.
let config: AppConfig = workspace?.load_config?; // Supports .toml, .json, .yaml
Resource Discovery (glob)
Find files with glob patterns like src/**/*.rs.
let rust_files = workspace?.find_resources?;
Secret Management (secrets)
Load secrets from secret/ directory with environment fallbacks. Supports both KEY=VALUE format and shell export KEY=VALUE statements.
let api_key = workspace?.load_secret_key?;
Memory-Safe Secret Handling (secure)
Advanced secret management with memory-safe SecretString types and automatic injection.
use ExposeSecret;
// Memory-safe secret loading
let secrets = workspace?.load_secrets_secure?;
let api_key = secrets.get.unwrap;
println!;
// Template-based secret injection into configuration files
let config = workspace?.load_config_with_secret_injection?;
// Secret strength validation
workspace?.validate_secret?; // Returns error for weak secrets
Config Validation (validation)
Schema-based validation for configuration files.
let config: AppConfig = workspace?.load_config_with_validation?;
🔐 Advanced Security Features
Type-Safe Secret Injection
The SecretInjectable trait allows automatic injection of secrets into configuration types with compile-time safety:
use ;
let ws = workspace?;
let mut config = AppConfig ;
config = ws.load_config_with_secrets?; // Automatically validates
Security Best Practices
- Memory Safety: All secrets wrapped in
SecretStringtypes that prevent accidental exposure - Debug Protection: Secrets are automatically redacted from debug output
- Explicit Access: Secrets require explicit
expose_secret()calls for access - Validation: Built-in secret strength validation rejects weak passwords
- Zeroization: Secrets are automatically cleared from memory when dropped
🛠️ Built for the Real World
workspace_tools is designed for production use, with features that support robust testing and flexible deployment.
Testing with Confidence
Create clean, isolated environments for your tests.
// In tests/my_test.rs
use create_test_workspace_with_structure;
use fs;
Flexible Deployment
Because workspace_tools can be configured via WORKSPACE_PATH, it adapts effortlessly to any environment.
Dockerfile:
# Your build stages...
# Final stage
FROM debian:bookworm-slim
WORKDIR /app
ENV WORKSPACE_PATH=/app # Set the workspace root inside the container.
COPY --from=builder /app/target/release/my-app .
COPY config/ ./config/
COPY assets/ ./assets/
CMD ["./my-app"] # Your app now runs with the correct workspace context.
Resilient by Design
workspace_tools has a smart fallback strategy to find your workspace root, ensuring it always finds a sensible path.
graph TD
A[Start] --> B{Cargo Workspace?};
B -->|Yes| C[Use Cargo Root];
B -->|No| D{WORKSPACE_PATH Env Var?};
D -->|Yes| E[Use Env Var Path];
D -->|No| F{.git folder nearby?};
F -->|Yes| G[Use Git Root];
F -->|No| H[Use Current Directory];
C --> Z[Success];
E --> Z[Success];
G --> Z[Success];
H --> Z[Success];
📚 API Reference
Core Methods
// Workspace creation and path operations
let ws = workspace?; // Auto-detect workspace root
let ws = new; // Explicit path
let path = ws.join; // Join paths safely
let root = ws.root; // Get workspace root
// Standard directories
let config = ws.config_dir; // ./config/
let data = ws.data_dir; // ./data/
let logs = ws.logs_dir; // ./logs/
let docs = ws.docs_dir; // ./docs/
Path Normalization
workspace_tools automatically normalizes all workspace root paths to ensure consistent behavior regardless of how the workspace is created:
// all workspace creation methods normalize paths
let ws = workspace?; // normalized automatically
let ws = new; // trailing "/." removed
let ws = from_cargo_workspace?; // normalized automatically
// path normalization guarantees:
// - absolute paths (relative paths are resolved against current directory)
// - no trailing "/." components
// - no "/./" components in the middle of paths
// - symlinks are preserved (not resolved to canonical paths)
// - empty WORKSPACE_PATH values are rejected with clear error
// examples of normalized paths:
// "/tmp/project/." → "/tmp/project"
// "/tmp/./project" → "/tmp/project"
// "./project" → "/absolute/cwd/project"
// "/tmp/foo/../project" → "/tmp/project"
This normalization ensures that:
- Path comparisons work correctly
- Joined paths remain clean (no accumulated dot components)
- Error messages show absolute paths for better debugging
- Behavior is consistent across different operating systems
Configuration Loading
// Load configuration files (supports .toml, .json, .yaml)
let config: MyConfig = ws.load_config?;
let config: MyConfig = ws.load_config_from?;
// Layered configuration (loads multiple files and merges)
let config: MyConfig = ws.load_config_layered?;
// Configuration with validation
let config: MyConfig = ws.load_config_with_validation?;
Secret Management
// Basic secret loading
let secrets = ws.load_secrets_from_file?;
let api_key = ws.load_secret_key?;
// Memory-safe secret handling (requires 'secure' feature)
let secrets = ws.load_secrets_secure?;
let api_key = ws.load_secret_key_secure?;
let token = ws.env_secret;
// Secret validation and injection
ws.validate_secret?; // Validates strength
let config_text = ws.load_config_with_secret_injection?;
let config: MyConfig = ws.load_config_with_secrets?;
Resource Discovery
// Find files with glob patterns (requires 'glob' feature)
let rust_files = ws.find_resources?;
let configs = ws.find_resources?;
// Find configuration files with priority ordering
let config_path = ws.find_config?; // Looks for app.toml, app.json, app.yaml
🏗️ Internal Architecture
workspace_tools follows strict design principles to ensure maintainability and code quality:
DRY Compliance Through Helper Functions
All configuration and validation operations are built on a foundation of reusable internal helpers that eliminate code duplication:
Format Detection and Parsing (serde feature)
detect_format()- detect file format from extension (toml/json/yaml)read_file_to_string()- read file with consistent error wrappingparse_content()- parse configuration based on detected formatserialize_content()- serialize configuration to target format
Validation Helpers (validation feature)
parse_to_json()- convert any format (toml/json/yaml) to JSON for validationvalidate_against_schema()- validate JSON against JSON Schema with detailed errors
These helpers provide:
- Single source of truth: Format handling logic exists in exactly one place
- Consistent error messages: All file operations produce uniform error context
- Easy extensibility: Adding new formats requires updating only 2-3 functions
- Reduced complexity: Public API functions reduced from 25-40 lines to 4-17 lines each
Type-Safe Secure Conversion Pattern
The secure feature uses a trait-based pattern for converting plain types to memory-protected types:
All _secure() methods follow the identical pattern:
Benefits:
- Zero duplication: All 5 secure wrappers share identical implementation pattern
- Type safety: Compiler enforces correct conversions
- Clear intent:
.map(AsSecure::into_secure)explicitly shows conversion - Extensible: New secure types only require implementing the trait
Code Quality Metrics
After comprehensive refactoring (2025-10-04):
- ~127 lines of duplicated code eliminated across 4 refactoring phases
- 13 functions simplified with 60% average complexity reduction
- 8 internal helpers providing single source of truth for common operations
- 100% DRY compliance in configuration, validation, secure conversion, and file I/O code
- Zero breaking changes: All refactoring internal to maintain API stability
Refactoring phases completed:
- Phase 1: Configuration/validation helpers (100 lines saved)
- Phase 2: Secure conversion trait pattern (15 lines saved)
- Phase 2.5: File reading consolidation (5 lines saved)
- Phase 2.6: Format detection consolidation (3 lines saved + removed obsolete helper)
🤝 Contributing
This project thrives on community contributions. Whether it's reporting a bug, suggesting a feature, or writing code, your help is welcome! Please see our task list and contribution guidelines.
⚖️ License
This project is licensed under the MIT License.