Skip to content

Commit 178df87

Browse files
DK26Copilot
andauthored
Release v0.4.3: Documentation & Discoverability Improvements (#38)
* Fix raw string escaping in doc comments and test examples (#34) * Initial plan * Fix double-escaped raw strings in doc comments and tests Co-authored-by: DK26 <12109801+DK26@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DK26 <12109801+DK26@users.noreply.github.com> * Enhance README and lib.rs documentation with details on UNC simplification and virtual filesystem support * Add documentation aliases for soft_canonicalize and anchored_canonicalize functions * Fix conditional compilation block in the Basic Example section for Windows * Bump version to 0.4.3 and update changelog with documentation improvements and enhancements --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: DK26 <12109801+DK26@users.noreply.github.com>
1 parent 667ad8c commit 178df87

7 files changed

Lines changed: 89 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.4.3] - 2025-10-11
11+
12+
### Added
13+
14+
- **Documentation discoverability improvements**
15+
- Added `#[doc(alias)]` attributes to improve API discoverability:
16+
- `soft_canonicalize`: aliases for `realpath`, `canonicalize`, `resolve`, `absolute`
17+
- `anchored_canonicalize`: aliases for `chroot`, `jail`, `sandbox`, `virtual_root`
18+
- `MAX_SYMLINK_DEPTH`: aliases for `ELOOP`, `symlink_limit`
19+
- Added `#[must_use]` attributes to `soft_canonicalize` and `anchored_canonicalize` to prevent accidental result dropping
20+
21+
### Changed
22+
23+
- **Documentation enhancements**
24+
- Enhanced "Why Use This?" section to mention `dunce` feature in compatibility bullet point
25+
- Enhanced "Why Use This?" section to highlight `anchored` feature for virtual filesystem support
26+
- Fixed cross-platform doctest compatibility by adding `#[cfg(windows)]` to Windows-specific Basic Example
27+
28+
### Fixed
29+
30+
- Fixed raw string escaping in doc comments and test examples ([#34](https://github.com/DK26/soft-canonicalize-rs/pull/34))
31+
1032
## [0.4.2] - 2025-10-08
1133

1234
### Added

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "soft-canonicalize"
3-
version = "0.4.2"
3+
version = "0.4.3"
44
edition = "2021"
55
authors = ["David Krasnitsky <dikaveman@gmail.com>"]
66
description = "Path canonicalization that works with non-existing paths."

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ Rust implementation inspired by Python 3.6+ `pathlib.Path.resolve(strict=False)`
1414

1515
**🚀 Works with non-existing paths** - Plan file locations before creating them
1616
**⚡ Fast** - Mixed workload median performance (5-run protocol): Windows ~1.3x (9,907 paths/s), Linux ~1.9x (238,038 paths/s) faster than Python's pathlib
17-
**✅ Compatible** - 100% behavioral match with `std::fs::canonicalize` for existing paths
18-
**🔒 Robust** - 436 comprehensive tests including symlink cycle protection, malicious stream validation, and edge case handling
17+
**✅ Compatible** - 100% behavioral match with `std::fs::canonicalize` for existing paths, with optional UNC simplification via `dunce` feature (Windows)
18+
**🎯 Virtual filesystem support** - Optional `anchored` feature for bounded canonicalization within directory boundaries
19+
**🔒 Robust** - 435 comprehensive tests including symlink cycle protection, malicious stream validation, and edge case handling
1920
**🛡️ Safe traversal** - Proper `..` and symlink resolution with cycle detection
2021
**🌍 Cross-platform** - Windows, macOS, Linux with comprehensive UNC/symlink handling
2122
**🔧 Zero dependencies** - Optional features may add dependencies
@@ -51,6 +52,12 @@ assert_eq!(
5152
result.unwrap().to_string_lossy(),
5253
r"\\?\C:\Users\user\non\existing\config.json"
5354
);
55+
56+
// With `dunce` feature enabled, paths are simplified when safe
57+
// assert_eq!(
58+
// result.unwrap().to_string_lossy(),
59+
// r"C:\Users\user\non\existing\config.json"
60+
// );
5461
```
5562

5663
## Optional Features

examples/basic_usage.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
//! Basic usage examples demonstrating soft_canonicalize with various path types.
2+
//!
3+
//! Run with: cargo run --example basic_usage
4+
//!
5+
//! This example shows:
6+
//! - Existing paths (matches std::fs::canonicalize)
7+
//! - Non-existing paths (extends beyond std behavior)
8+
//! - Relative paths (converted to absolute)
9+
//! - Directory traversal (.. and . resolved)
10+
111
use soft_canonicalize::soft_canonicalize;
212
use std::path::Path;
313

src/lib.rs

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
//!
1212
//! - **🚀 Works with non-existing paths** - Plan file locations before creating them
1313
//! - **⚡ Fast** - Optimized performance with minimal allocations and syscalls
14-
//! - **✅ Compatible** - 100% behavioral match with `std::fs::canonicalize` for existing paths
15-
//! - **🔒 Robust** - 436 comprehensive tests covering edge cases and security scenarios
14+
//! - **✅ Compatible** - 100% behavioral match with `std::fs::canonicalize` for existing paths, with optional UNC simplification via `dunce` feature (Windows)
15+
//! - **🎯 Virtual filesystem support** - Optional `anchored` feature for bounded canonicalization within directory boundaries
16+
//! - **🔒 Robust** - 435 comprehensive tests covering edge cases and security scenarios
1617
//! - **🛡️ Safe traversal** - Proper `..` and symlink resolution with cycle detection
1718
//! - **🌍 Cross-platform** - Windows, macOS, Linux with comprehensive UNC/symlink handling
1819
//! - **🔧 Zero dependencies** - Optional features may add dependencies
@@ -24,36 +25,39 @@
2425
//! soft-canonicalize = "0.4"
2526
//! ```
2627
//!
27-
//! ### Cross-Platform Example
28+
//! ### Basic Example
2829
//!
2930
//! ```rust
30-
//! use soft_canonicalize::soft_canonicalize;
31-
//!
32-
//! // Existing path behaves like std::fs::canonicalize
33-
//! let existing = soft_canonicalize(&std::env::temp_dir())?;
34-
//! # let _ = existing;
35-
//!
36-
//! // Also works when suffixes don't exist yet
37-
//! let non_existing = soft_canonicalize(
38-
//! std::env::temp_dir().join("some/deep/non/existing/path.txt")
39-
//! )?;
40-
//! # let _ = non_existing;
41-
//! # Ok::<(), std::io::Error>(())
42-
//! ```
43-
//!
44-
//! ### Windows Example (UNC/extended-length)
45-
//!
46-
//! ```rust
47-
//! use soft_canonicalize::soft_canonicalize;
48-
//! # fn example() -> Result<(), std::io::Error> {
4931
//! # #[cfg(windows)]
5032
//! # {
51-
//! let p = r"C:\\Users\\user\\documents\\..\\non\\existing\\config.json";
52-
//! let result = soft_canonicalize(p)?;
53-
//! assert!(result.to_string_lossy().starts_with(r"\\\\?\\C:"));
54-
//! # }
55-
//! # Ok(())
33+
//! use soft_canonicalize::soft_canonicalize;
34+
//! use std::path::PathBuf;
35+
//!
36+
//! let non_existing_path = r"C:\Users\user\documents\..\non\existing\config.json";
37+
//!
38+
//! // Using Rust's own std canonicalize function:
39+
//! let result = std::fs::canonicalize(non_existing_path);
40+
//! assert!(result.is_err());
41+
//!
42+
//! // Using our crate's function:
43+
//! let result = soft_canonicalize(non_existing_path);
44+
//! assert!(result.is_ok());
45+
//!
46+
//! // Shows the UNC path conversion and path normalization
47+
//! # #[cfg(not(feature = "dunce"))]
48+
//! assert_eq!(
49+
//! result.unwrap().to_string_lossy(),
50+
//! r"\\?\C:\Users\user\non\existing\config.json"
51+
//! );
52+
//!
53+
//! // With `dunce` feature enabled, paths are simplified when safe
54+
//! # #[cfg(feature = "dunce")]
55+
//! assert_eq!(
56+
//! result.unwrap().to_string_lossy(),
57+
//! r"C:\Users\user\non\existing\config.json"
58+
//! );
5659
//! # }
60+
//! # Ok::<(), std::io::Error>(())
5761
//! ```
5862
//!
5963
//! ## How It Works
@@ -68,6 +72,7 @@
6872
//! 8. Optionally canonicalize the anchor (if symlinks seen) and rebuild
6973
//! 9. Append non-existing suffix lexically, then normalize if needed
7074
//! 10. Windows: ensure extended-length prefix for absolute paths
75+
//! 11. Optional: simplify Windows paths when `dunce` feature enabled
7176
//!
7277
//! ## Security Considerations
7378
//!
@@ -195,7 +200,7 @@
195200
//!
196201
//! ## Testing
197202
//!
198-
//! 436 tests including:
203+
//! 435 tests including:
199204
//! - std::fs::canonicalize compatibility tests (existing paths)
200205
//! - Path traversal and robustness tests
201206
//! - Python pathlib-inspired behavior checks
@@ -298,6 +303,11 @@ fn reject_nul_bytes(p: &Path) -> io::Result<()> {
298303
/// - Unix: Returns standard absolute paths (`/foo`) - no change
299304
///
300305
/// See the [module documentation](crate#optional-features) for details on the `dunce` feature.
306+
#[must_use = "this function returns a new PathBuf without modifying the input"]
307+
#[doc(alias = "realpath")]
308+
#[doc(alias = "canonicalize")]
309+
#[doc(alias = "resolve")]
310+
#[doc(alias = "absolute")]
301311
pub fn soft_canonicalize(path: impl AsRef<Path>) -> io::Result<PathBuf> {
302312
let path = path.as_ref();
303313

@@ -615,6 +625,11 @@ pub fn soft_canonicalize(path: impl AsRef<Path>) -> io::Result<PathBuf> {
615625
/// # #[cfg(unix)]
616626
/// # demo().unwrap();
617627
/// ```
628+
#[must_use = "this function returns a new PathBuf without modifying the input"]
629+
#[doc(alias = "chroot")]
630+
#[doc(alias = "jail")]
631+
#[doc(alias = "sandbox")]
632+
#[doc(alias = "virtual_root")]
618633
#[cfg(feature = "anchored")]
619634
#[cfg_attr(docsrs, doc(cfg(feature = "anchored")))]
620635
pub fn anchored_canonicalize(

src/symlink.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use crate::normalize::simple_normalize_path;
99
/// - Linux: ELOOP limit is typically 40
1010
/// - Windows: Similar limit around 63
1111
/// - Other Unix systems: Usually 32-40
12+
#[doc(alias = "ELOOP")]
13+
#[doc(alias = "symlink_limit")]
1214
pub const MAX_SYMLINK_DEPTH: usize = if cfg!(target_os = "windows") { 63 } else { 40 };
1315

1416
/// Strip root prefix from an absolute path to make it relative.

tests/blackbox_complex_attacks.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ fn test_windows_short_name_bypass() -> std::io::Result<()> {
194194
fn test_windows_device_name_bypass() -> std::io::Result<()> {
195195
// These paths should be handled correctly and not cause panics.
196196
let device_paths = vec![
197-
r"\\.\\C:\\windows\\system32\\kernel32.dll",
198-
r"\\\\?\\C:\\windows\\system32\\kernel32.dll",
197+
r"\\.\C:\windows\system32\kernel32.dll",
198+
r"\\?\C:\windows\system32\kernel32.dll",
199199
"CON",
200200
"NUL",
201201
"COM1",

0 commit comments

Comments
 (0)