1use alloc::string::String;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub enum ErrorKind {
8 Empty,
10
11 CurrentDirectoryMarker,
13
14 ParentDirectoryMarker,
16
17 ContainsForwardSlash,
19
20 ContainsNullByte,
23
24 InvalidUtf8,
26
27 ContainsUnassignedChar,
30
31 #[cfg(target_vendor = "apple")]
34 GetFileSystemRepresentationError,
35}
36
37impl ErrorKind {
38 pub(crate) fn into_error(self, original: impl Into<String>) -> Error {
40 Error {
41 original: original.into(),
42 kind: self,
43 }
44 }
45}
46
47impl core::fmt::Display for ErrorKind {
48 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
49 match self {
50 Self::Empty => f.write_str("empty path element"),
51 Self::CurrentDirectoryMarker => f.write_str("current directory marker"),
52 Self::ParentDirectoryMarker => f.write_str("parent directory marker"),
53 Self::ContainsForwardSlash => f.write_str("contains forward slash"),
54 Self::ContainsNullByte => f.write_str("contains null byte"),
55 Self::InvalidUtf8 => f.write_str("invalid UTF-8"),
56 Self::ContainsUnassignedChar => f.write_str("contains unassigned character"),
57 #[cfg(target_vendor = "apple")]
58 Self::GetFileSystemRepresentationError => {
59 f.write_str("CFStringGetFileSystemRepresentation failed")
60 }
61 }
62 }
63}
64
65#[derive(Debug)]
85pub struct Error {
86 original: String,
87 kind: ErrorKind,
88}
89
90impl Error {
91 #[must_use]
93 pub fn kind(&self) -> ErrorKind {
94 self.kind
95 }
96
97 #[must_use]
99 pub fn original(&self) -> &str {
100 &self.original
101 }
102
103 #[must_use]
105 pub fn into_original(self) -> String {
106 self.original
107 }
108}
109
110impl core::fmt::Display for Error {
111 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
112 if self.original.is_empty() {
113 write!(f, "{}", self.kind)
114 } else {
115 write!(f, "{}: {:?}", self.kind, self.original)
116 }
117 }
118}
119
120impl core::error::Error for Error {}
121
122pub type Result<T> = core::result::Result<T, Error>;
124
125pub type ResultKind<T> = core::result::Result<T, ErrorKind>;
131
132#[cfg(test)]
133mod tests {
134 use alloc::format;
135 use alloc::string::{String, ToString};
136
137 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
138 use wasm_bindgen_test::wasm_bindgen_test as test;
139
140 use super::ErrorKind;
141
142 #[test]
143 fn error_kind_display() {
144 assert_eq!(ErrorKind::Empty.to_string(), "empty path element");
145 assert_eq!(
146 ErrorKind::CurrentDirectoryMarker.to_string(),
147 "current directory marker"
148 );
149 assert_eq!(
150 ErrorKind::ParentDirectoryMarker.to_string(),
151 "parent directory marker"
152 );
153 assert_eq!(
154 ErrorKind::ContainsForwardSlash.to_string(),
155 "contains forward slash"
156 );
157 assert_eq!(
158 ErrorKind::ContainsNullByte.to_string(),
159 "contains null byte"
160 );
161 assert_eq!(ErrorKind::InvalidUtf8.to_string(), "invalid UTF-8");
162 assert_eq!(
163 ErrorKind::ContainsUnassignedChar.to_string(),
164 "contains unassigned character"
165 );
166 }
167
168 #[test]
169 fn into_error_stores_original() {
170 let err = ErrorKind::Empty.into_error(String::from(" "));
171 assert_eq!(err.kind(), ErrorKind::Empty);
172 assert_eq!(err.original(), " ");
173 }
174
175 #[test]
176 fn into_error_empty_original() {
177 let err = ErrorKind::Empty.into_error(String::new());
178 assert_eq!(err.kind(), ErrorKind::Empty);
179 assert_eq!(err.original(), "");
180 }
181
182 #[test]
183 fn kind_roundtrip() {
184 let err = ErrorKind::ContainsNullByte.into_error(String::from("a\0b"));
185 assert_eq!(err.kind(), ErrorKind::ContainsNullByte);
186 }
187
188 #[test]
189 fn into_original() {
190 let err = ErrorKind::Empty.into_error(String::from(" "));
191 assert_eq!(err.into_original(), " ");
192 }
193
194 #[test]
195 fn error_display_with_original() {
196 let err = ErrorKind::ContainsForwardSlash.into_error(String::from("a/b"));
197 assert_eq!(format!("{err}"), "contains forward slash: \"a/b\"");
198 }
199
200 #[test]
201 fn error_display_empty_original() {
202 let err = ErrorKind::Empty.into_error(String::new());
203 assert_eq!(format!("{err}"), "empty path element");
204 }
205
206 #[test]
207 fn error_debug() {
208 let err = ErrorKind::Empty.into_error(String::from("."));
209 let debug = format!("{err:?}");
210 assert!(debug.contains("Empty"));
211 assert!(debug.contains('.'));
212 }
213
214 #[test]
215 fn path_element_error_has_original() {
216 let err = crate::PathElementCS::new("a/b").unwrap_err();
217 assert_eq!(err.kind(), ErrorKind::ContainsForwardSlash);
218 assert_eq!(err.original(), "a/b");
219 }
220
221 #[test]
222 fn path_element_error_empty() {
223 let err = crate::PathElementCS::new("").unwrap_err();
224 assert_eq!(err.kind(), ErrorKind::Empty);
225 assert_eq!(err.original(), "");
226 }
227
228 #[test]
229 fn path_element_error_dot() {
230 let err = crate::PathElementCI::new(".").unwrap_err();
231 assert_eq!(err.kind(), ErrorKind::CurrentDirectoryMarker);
232 assert_eq!(err.original(), ".");
233 }
234
235 #[test]
236 fn path_element_error_dotdot() {
237 let err = crate::PathElementCS::new("..").unwrap_err();
238 assert_eq!(err.kind(), ErrorKind::ParentDirectoryMarker);
239 assert_eq!(err.original(), "..");
240 }
241
242 #[test]
243 fn path_element_error_null_byte() {
244 let err = crate::PathElementCS::new("a\0b").unwrap_err();
245 assert_eq!(err.kind(), ErrorKind::ContainsNullByte);
246 assert_eq!(err.original(), "a\0b");
247 }
248
249 #[test]
250 fn path_element_error_whitespace_trimmed_to_empty() {
251 let err = crate::PathElementCS::new(" ").unwrap_err();
252 assert_eq!(err.kind(), ErrorKind::Empty);
253 assert_eq!(err.original(), " ");
254 }
255
256 #[test]
257 fn path_element_error_bom_trimmed_to_dot() {
258 let err = crate::PathElementCS::new("\u{FEFF}.").unwrap_err();
259 assert_eq!(err.kind(), ErrorKind::CurrentDirectoryMarker);
260 assert_eq!(err.original(), "\u{FEFF}.");
261 }
262}