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)]
78pub struct Error {
79 original: String,
80 kind: ErrorKind,
81}
82
83impl Error {
84 #[must_use]
86 pub fn kind(&self) -> &ErrorKind {
87 &self.kind
88 }
89
90 #[must_use]
92 pub fn into_kind(self) -> ErrorKind {
93 self.kind
94 }
95
96 #[must_use]
98 pub fn original(&self) -> &str {
99 &self.original
100 }
101}
102
103impl core::fmt::Display for Error {
104 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
105 if self.original.is_empty() {
106 write!(f, "{}", self.kind)
107 } else {
108 write!(f, "{}: {:?}", self.kind, self.original)
109 }
110 }
111}
112
113impl core::error::Error for Error {}
114
115pub type Result<T> = core::result::Result<T, Error>;
117
118pub type ResultKind<T> = core::result::Result<T, ErrorKind>;
124
125#[cfg(test)]
126mod tests {
127 use alloc::format;
128 use alloc::string::{String, ToString};
129
130 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
131 use wasm_bindgen_test::wasm_bindgen_test as test;
132
133 use super::ErrorKind;
134
135 #[test]
136 fn error_kind_display() {
137 assert_eq!(ErrorKind::Empty.to_string(), "empty path element");
138 assert_eq!(
139 ErrorKind::CurrentDirectoryMarker.to_string(),
140 "current directory marker"
141 );
142 assert_eq!(
143 ErrorKind::ParentDirectoryMarker.to_string(),
144 "parent directory marker"
145 );
146 assert_eq!(
147 ErrorKind::ContainsForwardSlash.to_string(),
148 "contains forward slash"
149 );
150 assert_eq!(
151 ErrorKind::ContainsNullByte.to_string(),
152 "contains null byte"
153 );
154 assert_eq!(ErrorKind::InvalidUtf8.to_string(), "invalid UTF-8");
155 assert_eq!(
156 ErrorKind::ContainsUnassignedChar.to_string(),
157 "contains unassigned character"
158 );
159 }
160
161 #[test]
162 fn into_error_stores_original() {
163 let err = ErrorKind::Empty.into_error(String::from(" "));
164 assert_eq!(err.original(), " ");
165 assert!(matches!(err.kind(), ErrorKind::Empty));
166 }
167
168 #[test]
169 fn into_error_empty_original() {
170 let err = ErrorKind::Empty.into_error(String::new());
171 assert_eq!(err.original(), "");
172 assert!(matches!(err.kind(), ErrorKind::Empty));
173 }
174
175 #[test]
176 fn into_kind_roundtrip() {
177 let err = ErrorKind::ContainsNullByte.into_error(String::from("a\0b"));
178 assert!(matches!(err.into_kind(), ErrorKind::ContainsNullByte));
179 }
180
181 #[test]
182 fn error_display_with_original() {
183 let err = ErrorKind::ContainsForwardSlash.into_error(String::from("a/b"));
184 assert_eq!(format!("{err}"), "contains forward slash: \"a/b\"");
185 }
186
187 #[test]
188 fn error_display_empty_original() {
189 let err = ErrorKind::Empty.into_error(String::new());
190 assert_eq!(format!("{err}"), "empty path element");
191 }
192
193 #[test]
194 fn error_debug() {
195 let err = ErrorKind::Empty.into_error(String::from("."));
196 let debug = format!("{err:?}");
197 assert!(debug.contains("Empty"));
198 assert!(debug.contains('.'));
199 }
200
201 #[test]
202 fn path_element_error_has_original() {
203 let err = crate::PathElementCS::new("a/b").unwrap_err();
204 assert!(matches!(err.kind(), ErrorKind::ContainsForwardSlash));
205 assert_eq!(err.original(), "a/b");
206 }
207
208 #[test]
209 fn path_element_error_empty() {
210 let err = crate::PathElementCS::new("").unwrap_err();
211 assert!(matches!(err.kind(), ErrorKind::Empty));
212 assert_eq!(err.original(), "");
213 }
214
215 #[test]
216 fn path_element_error_dot() {
217 let err = crate::PathElementCI::new(".").unwrap_err();
218 assert!(matches!(err.kind(), ErrorKind::CurrentDirectoryMarker));
219 assert_eq!(err.original(), ".");
220 }
221
222 #[test]
223 fn path_element_error_dotdot() {
224 let err = crate::PathElementCS::new("..").unwrap_err();
225 assert!(matches!(err.kind(), ErrorKind::ParentDirectoryMarker));
226 assert_eq!(err.original(), "..");
227 }
228
229 #[test]
230 fn path_element_error_null_byte() {
231 let err = crate::PathElementCS::new("a\0b").unwrap_err();
232 assert!(matches!(err.kind(), ErrorKind::ContainsNullByte));
233 assert_eq!(err.original(), "a\0b");
234 }
235
236 #[test]
237 fn path_element_error_whitespace_trimmed_to_empty() {
238 let err = crate::PathElementCS::new(" ").unwrap_err();
239 assert!(matches!(err.kind(), ErrorKind::Empty));
240 assert_eq!(err.original(), " ");
241 }
242
243 #[test]
244 fn path_element_error_bom_trimmed_to_dot() {
245 let err = crate::PathElementCS::new("\u{FEFF}.").unwrap_err();
246 assert!(matches!(err.kind(), ErrorKind::CurrentDirectoryMarker));
247 assert_eq!(err.original(), "\u{FEFF}.");
248 }
249}