strict_path/error/
mod.rs

1//! SUMMARY:
2//! Define error types and helpers for boundary creation and strict/virtual path validation.
3//!
4//! OVERVIEW:
5//! This module exposes the crate-wide error enum `StrictPathError`, which captures
6//! boundary creation failures, path resolution errors, boundary escape attempts,
7//! and (on Windows) 8.3 short-name rejections. These errors are surfaced by
8//! public constructors and join operations throughout the crate.
9//!
10//! STYLE:
11//! All items follow the standardized doc format with explicit sections to keep
12//! behavior unambiguous for both humans and LLMs.
13// Content copied from original src/error/mod.rs
14use std::error::Error;
15use std::fmt;
16use std::path::{Path, PathBuf};
17
18const MAX_ERROR_PATH_LEN: usize = 256;
19
20// Internal helper: render error-friendly path display (truncate long values).
21pub(crate) fn truncate_path_display(path: &Path, max_len: usize) -> String {
22    let path_str = path.to_string_lossy();
23    let char_count = path_str.chars().count();
24    if char_count <= max_len {
25        return path_str.into_owned();
26    }
27    let keep = max_len.saturating_sub(5) / 2;
28    let start: String = path_str.chars().take(keep).collect();
29    let mut tail_chars: Vec<char> = path_str.chars().rev().take(keep).collect();
30    tail_chars.reverse();
31    let end: String = tail_chars.into_iter().collect();
32    format!("{start}...{end}")
33}
34
35/// SUMMARY:
36/// Represent errors produced by boundary creation and strict/virtual path validation.
37///
38/// DETAILS:
39/// This error type is returned by operations that construct `PathBoundary`
40///`VirtualRoot` or that compose `StrictPath`/`VirtualPath` via joins. Each
41/// variant carries enough context for actionable diagnostics while avoiding
42/// leaking unbounded path data into messages (we truncate long displays).
43///
44/// VARIANTS:
45/// - `InvalidRestriction`: The boundary directory is missing, not a directory, or failed I/O checks.
46/// - `PathEscapesBoundary`: A candidate path would resolve outside the boundary.
47/// - `PathResolutionError`: Canonicalization or resolution failed (I/O error).
48#[derive(Debug)]
49pub enum StrictPathError {
50    /// SUMMARY:
51    /// The boundary directory is invalid (missing, not a directory, or I/O error).
52    ///
53    /// FIELDS:
54    /// - `restriction` (`PathBuf`): The attempted boundary path.
55    /// - `source` (`std::io::Error`): Underlying OS error that explains why the
56    ///   restriction is invalid.
57    InvalidRestriction {
58        restriction: PathBuf,
59        source: std::io::Error,
60    },
61    /// SUMMARY:
62    /// The attempted path would resolve outside the PathBoundary boundary.
63    ///
64    /// FIELDS:
65    /// - `attempted_path` (`PathBuf`): The user-supplied or composed candidate.
66    /// - `restriction_boundary` (`PathBuf`): The effective boundary root.
67    PathEscapesBoundary {
68        attempted_path: PathBuf,
69        restriction_boundary: PathBuf,
70    },
71    /// SUMMARY:
72    /// Canonicalization/resolution failed for the given path.
73    ///
74    /// FIELDS:
75    /// - `path` (`PathBuf`): The path whose resolution failed.
76    /// - `source` (`std::io::Error`): Underlying I/O cause.
77    PathResolutionError {
78        path: PathBuf,
79        source: std::io::Error,
80    },
81}
82
83impl StrictPathError {
84    // Internal helper: construct `InvalidRestriction`.
85    #[inline]
86    pub(crate) fn invalid_restriction(restriction: PathBuf, source: std::io::Error) -> Self {
87        Self::InvalidRestriction {
88            restriction,
89            source,
90        }
91    }
92    // Internal helper: construct `PathEscapesBoundary`.
93    #[inline]
94    pub(crate) fn path_escapes_boundary(
95        attempted_path: PathBuf,
96        restriction_boundary: PathBuf,
97    ) -> Self {
98        Self::PathEscapesBoundary {
99            attempted_path,
100            restriction_boundary,
101        }
102    }
103    // Internal helper: construct `PathResolutionError`.
104    #[inline]
105    pub(crate) fn path_resolution_error(path: PathBuf, source: std::io::Error) -> Self {
106        Self::PathResolutionError { path, source }
107    }
108}
109
110impl fmt::Display for StrictPathError {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        match self {
113            StrictPathError::InvalidRestriction { restriction, .. } => {
114                write!(
115                    f,
116                    "Invalid PathBoundary directory: {}",
117                    restriction.display()
118                )
119            }
120            StrictPathError::PathEscapesBoundary {
121                attempted_path,
122                restriction_boundary,
123            } => {
124                let truncated_attempted = truncate_path_display(attempted_path, MAX_ERROR_PATH_LEN);
125                let truncated_boundary =
126                    truncate_path_display(restriction_boundary, MAX_ERROR_PATH_LEN);
127                write!(
128                    f,
129                    "Path '{truncated_attempted}' escapes path restriction boundary '{truncated_boundary}'"
130                )
131            }
132            StrictPathError::PathResolutionError { path, .. } => {
133                write!(f, "Cannot resolve path: {}", path.display())
134            }
135        }
136    }
137}
138
139impl Error for StrictPathError {
140    fn source(&self) -> Option<&(dyn Error + 'static)> {
141        match self {
142            StrictPathError::InvalidRestriction { source, .. }
143            | StrictPathError::PathResolutionError { source, .. } => Some(source),
144            StrictPathError::PathEscapesBoundary { .. } => None,
145        }
146    }
147}
148
149#[cfg(test)]
150mod tests;