Skip to main content

path_rs/
error.rs

1//! Structured error type for `path-rs` operations.
2
3use std::io;
4use std::path::Path;
5
6/// Primary error type returned by `path-rs` APIs.
7///
8/// Errors include enough context to diagnose the failing path or operation.
9/// Third-party error types are not exposed as public fields unless necessary.
10#[derive(Debug, thiserror::Error)]
11#[non_exhaustive]
12pub enum PathError {
13    /// The provided path input was empty after optional trimming.
14    #[error("path input is empty")]
15    EmptyInput,
16
17    /// The path contains an embedded NUL byte, which is invalid on all supported platforms.
18    #[error("path contains an embedded NUL byte")]
19    EmbeddedNul,
20
21    /// An environment variable referenced during expansion is not defined.
22    #[error("environment variable is not defined: {name}")]
23    UndefinedEnvironmentVariable {
24        /// Name of the missing variable.
25        name: String,
26    },
27
28    /// Environment variable syntax was malformed (e.g. unclosed `%VAR` or `${VAR`).
29    #[error("environment variable syntax is malformed: {input}")]
30    MalformedEnvironmentVariable {
31        /// The malformed input fragment.
32        input: String,
33    },
34
35    /// Home directory could not be determined.
36    #[error("home directory is unavailable")]
37    HomeDirectoryUnavailable,
38
39    /// Current working directory could not be determined.
40    #[error("current directory is unavailable: {source}")]
41    CurrentDirectoryUnavailable {
42        /// Underlying I/O error.
43        #[source]
44        source: io::Error,
45    },
46
47    /// A Windows drive-relative path such as `C:foo` or `C:` was encountered.
48    #[error("drive-relative Windows path is not supported: {path}")]
49    DriveRelativePath {
50        /// Display form of the path.
51        path: String,
52    },
53
54    /// An absolute child path was supplied where a relative path is required.
55    #[error("absolute child path is not allowed: {path}")]
56    AbsoluteChildPath {
57        /// Display form of the path.
58        path: String,
59    },
60
61    /// A path escapes the permitted root after normalization.
62    #[error("path escapes the permitted root: {path}")]
63    RootEscape {
64        /// Display form of the path.
65        path: String,
66    },
67
68    /// A path is not valid UTF-8 and a UTF-8 conversion was requested.
69    #[error("path is not valid UTF-8")]
70    NotUtf8,
71
72    /// Generic invalid path condition with a human-readable message.
73    #[error("invalid path: {message}")]
74    InvalidPath {
75        /// Description of the problem.
76        message: String,
77    },
78
79    /// A filesystem operation failed.
80    #[error("filesystem operation failed for {path}: {source}")]
81    Filesystem {
82        /// Display form of the path involved.
83        path: String,
84        /// Underlying I/O error.
85        #[source]
86        source: io::Error,
87    },
88
89    /// Directory traversal failed (permission, depth limit, etc.).
90    #[error("directory traversal failed: {message}")]
91    Traversal {
92        /// Description of the failure.
93        message: String,
94    },
95
96    /// A glob pattern is invalid.
97    #[error("glob pattern is invalid: {message}")]
98    InvalidGlob {
99        /// Description of the pattern error.
100        message: String,
101    },
102
103    /// A cache operation failed.
104    #[error("cache operation failed: {message}")]
105    Cache {
106        /// Description of the failure.
107        message: String,
108    },
109
110    /// Application name validation failed.
111    #[error("invalid application name: {message}")]
112    InvalidAppName {
113        /// Description of the problem.
114        message: String,
115    },
116
117    /// Expansion exceeded the maximum allowed depth.
118    #[error("path expansion exceeded maximum depth ({max_depth})")]
119    ExpansionDepthExceeded {
120        /// Configured maximum depth.
121        max_depth: u32,
122    },
123}
124
125impl PathError {
126    /// Create a filesystem error for `path` wrapping `source`.
127    pub fn filesystem(path: impl AsRef<Path>, source: io::Error) -> Self {
128        Self::Filesystem {
129            path: path_display(path.as_ref()),
130            source,
131        }
132    }
133
134    /// Create an invalid-path error with a message.
135    pub fn invalid(message: impl Into<String>) -> Self {
136        Self::InvalidPath {
137            message: message.into(),
138        }
139    }
140
141    /// Create a traversal error with a message.
142    pub fn traversal(message: impl Into<String>) -> Self {
143        Self::Traversal {
144            message: message.into(),
145        }
146    }
147
148    /// Create a cache error with a message.
149    pub fn cache(message: impl Into<String>) -> Self {
150        Self::Cache {
151            message: message.into(),
152        }
153    }
154
155    /// Create a root-escape error for `path`.
156    pub fn root_escape(path: impl AsRef<Path>) -> Self {
157        Self::RootEscape {
158            path: path_display(path.as_ref()),
159        }
160    }
161
162    /// Create an absolute-child error for `path`.
163    pub fn absolute_child(path: impl AsRef<Path>) -> Self {
164        Self::AbsoluteChildPath {
165            path: path_display(path.as_ref()),
166        }
167    }
168
169    /// Create a drive-relative error for `path`.
170    pub fn drive_relative(path: impl AsRef<Path>) -> Self {
171        Self::DriveRelativePath {
172            path: path_display(path.as_ref()),
173        }
174    }
175}
176
177/// Lossy display string for diagnostics only (never for filesystem round-trips).
178pub(crate) fn path_display(path: &Path) -> String {
179    path.to_string_lossy().into_owned()
180}