| Crates.io | soft-canonicalize |
| lib.rs | soft-canonicalize |
| version | 0.3.6 |
| created_at | 2025-07-18 12:43:29.631991+00 |
| updated_at | 2025-09-15 10:29:16.45491+00 |
| description | Path canonicalization that works with non-existing paths. |
| homepage | https://github.com/DK26/soft-canonicalize-rs |
| repository | https://github.com/DK26/soft-canonicalize-rs |
| max_upload_size | |
| id | 1758927 |
| size | 628,272 |
Path canonicalization that works with non-existing paths.
Inspired by Python 3.6+ pathlib.Path.resolve(strict=False) - this library brings the same functionality to Rust with enhanced performance and comprehensive testing.
🚀 Works with non-existing paths - Plan file locations before creating them
⚡ Fast - Mixed workload median performance (5-run protocol): Windows ~1.8x (10,217 paths/s), Linux ~3.8x (352,751 paths/s) faster than Python's pathlib
✅ Compatible - 100% behavioral match with std::fs::canonicalize for existing paths
🔒 Robust - 301 comprehensive tests including symlink cycle protection, malicious stream validation, and edge case handling
🛡️ Robust path handling - Proper .. and symlink resolution with cycle detection
🌍 Cross-platform - Windows, macOS, Linux with comprehensive UNC/symlink handling
🔧 Zero dependencies - Only uses std library
For detailed benchmarks, analysis, and testing procedures, see the benches/ directory. Bench numbers vary by hardware, OS, and filesystem; see the bench outputs for per-scenario numbers.
[dependencies]
soft-canonicalize = "0.3"
use soft_canonicalize::soft_canonicalize;
use std::path::PathBuf;
let non_existing_path = r"C:\Users\user\documents\..\non\existing\config.json";
// Using Rust's own std canonicalize function:
let result = std::fs::canonicalize(non_existing_path);
assert!(result.is_err());
// Using our crate's function:
let result = soft_canonicalize(non_existing_path);
assert!(result.is_ok());
// Shows the UNC path conversion and path normalization
assert_eq!(
result.unwrap().to_string_lossy(),
r"\\?\C:\Users\user\non\existing\config.json"
);
For correct symlink resolution within virtual/constrained directory spaces, use anchored_canonicalize. This function ensures symlinks resolve properly relative to an anchor directory, making it ideal for virtual filesystems, containerized environments, and chroot-like scenarios:
use soft_canonicalize::anchored_canonicalize;
use std::fs;
// Set up an anchor/root directory (no need to pre-canonicalize)
let anchor = std::env::temp_dir().join("workspace_root");
fs::create_dir_all(&anchor)?;
// Canonicalize paths relative to the anchor (anchor is soft-canonicalized internally)
let resolved_path = anchored_canonicalize(&anchor, "../../../etc/passwd")?;
// Result: /tmp/workspace_root/etc/passwd (lexical .. clamped to anchor)
// Handles symlinks with proper canonicalization within virtual space
let user_input = "uploads/../../sensitive/file.txt";
let resolved = anchored_canonicalize(&anchor, user_input)?;
// Canonicalizes paths relative to anchor - symlinks are followed to their targets
Key features of anchored_canonicalize:
301 comprehensive tests including:
Our comprehensive test suite validates consistent canonicalization behavior across various challenging scenarios:
.. resolution with cycle detection and boundary enforcementPath canonicalization converts paths to their canonical (standard) form, enabling accurate comparison and ensuring two different path representations that point to the same location are recognized as equivalent.
Unlike std::fs::canonicalize(), this library resolves and normalizes paths even when components don't exist on the filesystem. This enables accurate path comparison, resolution of future file locations, and preprocessing paths before file creation.
This is essential for:
The "soft" aspect means we can canonicalize paths even when the target doesn't exist yet - extending traditional canonicalization to work with planned or future file locations.
Each crate serves different use cases. Choose based on your primary need:
| Crate | Primary Purpose | Use Cases |
|---|---|---|
soft_canonicalize |
Path canonicalization + non-existing paths | When you need std::fs::canonicalize behavior but for paths that don't exist yet |
std::fs::canonicalize |
Path canonicalization (existing paths only) | Standard path canonicalization when all paths exist on filesystem |
dunce::canonicalize |
Windows compatibility layer | Fixing Windows UNC issues for legacy app compatibility |
normpath::normalize |
Safe normalization alternative | Avoiding Windows UNC bugs while normalizing paths |
path_absolutize |
CWD-relative path resolution | Converting relative paths to absolute with performance optimization |
strict-path |
Type-safe path restriction with safe symlinks | Preventing directory traversal with type-safe path restriction and safe symlinks |
| Feature | soft_canonicalize |
std::fs::canonicalize |
dunce::canonicalize |
normpath::normalize |
path_absolutize |
strict-path |
|---|---|---|---|---|---|---|
| Works with non-existing paths | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ (via this crate) |
| Resolves symlinks | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ (via this crate) |
| Windows UNC path support | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ (via this crate) |
| Zero dependencies | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ (uses this crate) |
| Virtual/bounded canonicalization | ✅ (anchored_canonicalize) |
❌ | ❌ | ❌ | ❌ | ✅ (VirtualRoot) |
On Windows, the filesystem may generate short filenames (8.3 format) for long directory names. For non-existing paths, this library cannot determine if a short filename form (e.g., PROGRA~1) and its corresponding long form (e.g., Program Files) refer to the same future location:
use soft_canonicalize::soft_canonicalize;
// These non-existing paths are treated as different (correctly)
let short_form = soft_canonicalize("C:/PROGRA~1/MyApp/config.json")?;
let long_form = soft_canonicalize("C:/Program Files/MyApp/config.json")?;
// They will NOT be equal because we cannot determine equivalence
// without filesystem existence
assert_ne!(short_form, long_form);
This is a fundamental limitation shared by Python's pathlib.Path.resolve(strict=False) and other path canonicalization libraries across languages. Short filename mapping only exists when files/directories are actually created by the filesystem.
For existing paths, this library correctly resolves short and long forms to the same canonical result, maintaining 100% compatibility with std::fs::canonicalize.
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Licensed under either of:
See CHANGELOG.md for a detailed history of changes.