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}