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 ContainsUnassignedChar,
27
28 #[cfg(target_vendor = "apple")]
31 GetFileSystemRepresentationError,
32}
33
34impl ErrorKind {
35 pub(crate) fn into_error(self, original: impl Into<String>) -> Error {
37 Error {
38 original: original.into(),
39 kind: self,
40 }
41 }
42}
43
44impl core::fmt::Display for ErrorKind {
45 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46 match self {
47 Self::Empty => f.write_str("empty path element"),
48 Self::CurrentDirectoryMarker => f.write_str("current directory marker"),
49 Self::ParentDirectoryMarker => f.write_str("parent directory marker"),
50 Self::ContainsForwardSlash => f.write_str("contains forward slash"),
51 Self::ContainsNullByte => f.write_str("contains null byte"),
52 Self::ContainsUnassignedChar => f.write_str("contains unassigned character"),
53 #[cfg(target_vendor = "apple")]
54 Self::GetFileSystemRepresentationError => {
55 f.write_str("CFStringGetFileSystemRepresentation failed")
56 }
57 }
58 }
59}
60
61#[derive(Debug)]
74pub struct Error {
75 original: String,
76 kind: ErrorKind,
77}
78
79impl Error {
80 #[must_use]
82 pub fn kind(&self) -> &ErrorKind {
83 &self.kind
84 }
85
86 #[must_use]
88 pub fn into_kind(self) -> ErrorKind {
89 self.kind
90 }
91
92 #[must_use]
94 pub fn original(&self) -> &str {
95 &self.original
96 }
97}
98
99impl core::fmt::Display for Error {
100 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
101 if self.original.is_empty() {
102 write!(f, "{}", self.kind)
103 } else {
104 write!(f, "{}: {:?}", self.kind, self.original)
105 }
106 }
107}
108
109impl core::error::Error for Error {}
110
111pub type Result<T> = core::result::Result<T, Error>;
113
114pub type ResultKind<T> = core::result::Result<T, ErrorKind>;
120
121#[cfg(test)]
122mod tests {
123 use alloc::format;
124 use alloc::string::{String, ToString};
125
126 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
127 use wasm_bindgen_test::wasm_bindgen_test as test;
128
129 use super::ErrorKind;
130
131 #[test]
132 fn error_kind_display() {
133 assert_eq!(ErrorKind::Empty.to_string(), "empty path element");
134 assert_eq!(
135 ErrorKind::CurrentDirectoryMarker.to_string(),
136 "current directory marker"
137 );
138 assert_eq!(
139 ErrorKind::ParentDirectoryMarker.to_string(),
140 "parent directory marker"
141 );
142 assert_eq!(
143 ErrorKind::ContainsForwardSlash.to_string(),
144 "contains forward slash"
145 );
146 assert_eq!(
147 ErrorKind::ContainsNullByte.to_string(),
148 "contains null byte"
149 );
150 assert_eq!(
151 ErrorKind::ContainsUnassignedChar.to_string(),
152 "contains unassigned character"
153 );
154 }
155
156 #[test]
157 fn into_error_stores_original() {
158 let err = ErrorKind::Empty.into_error(String::from(" "));
159 assert_eq!(err.original(), " ");
160 assert!(matches!(err.kind(), ErrorKind::Empty));
161 }
162
163 #[test]
164 fn into_error_empty_original() {
165 let err = ErrorKind::Empty.into_error(String::new());
166 assert_eq!(err.original(), "");
167 assert!(matches!(err.kind(), ErrorKind::Empty));
168 }
169
170 #[test]
171 fn into_kind_roundtrip() {
172 let err = ErrorKind::ContainsNullByte.into_error(String::from("a\0b"));
173 assert!(matches!(err.into_kind(), ErrorKind::ContainsNullByte));
174 }
175
176 #[test]
177 fn error_display_with_original() {
178 let err = ErrorKind::ContainsForwardSlash.into_error(String::from("a/b"));
179 assert_eq!(format!("{err}"), "contains forward slash: \"a/b\"");
180 }
181
182 #[test]
183 fn error_display_empty_original() {
184 let err = ErrorKind::Empty.into_error(String::new());
185 assert_eq!(format!("{err}"), "empty path element");
186 }
187
188 #[test]
189 fn error_debug() {
190 let err = ErrorKind::Empty.into_error(String::from("."));
191 let debug = format!("{err:?}");
192 assert!(debug.contains("Empty"));
193 assert!(debug.contains('.'));
194 }
195
196 #[test]
197 fn path_element_error_has_original() {
198 let err = crate::PathElementCS::new("a/b").unwrap_err();
199 assert!(matches!(err.kind(), ErrorKind::ContainsForwardSlash));
200 assert_eq!(err.original(), "a/b");
201 }
202
203 #[test]
204 fn path_element_error_empty() {
205 let err = crate::PathElementCS::new("").unwrap_err();
206 assert!(matches!(err.kind(), ErrorKind::Empty));
207 assert_eq!(err.original(), "");
208 }
209
210 #[test]
211 fn path_element_error_dot() {
212 let err = crate::PathElementCI::new(".").unwrap_err();
213 assert!(matches!(err.kind(), ErrorKind::CurrentDirectoryMarker));
214 assert_eq!(err.original(), ".");
215 }
216
217 #[test]
218 fn path_element_error_dotdot() {
219 let err = crate::PathElementCS::new("..").unwrap_err();
220 assert!(matches!(err.kind(), ErrorKind::ParentDirectoryMarker));
221 assert_eq!(err.original(), "..");
222 }
223
224 #[test]
225 fn path_element_error_null_byte() {
226 let err = crate::PathElementCS::new("a\0b").unwrap_err();
227 assert!(matches!(err.kind(), ErrorKind::ContainsNullByte));
228 assert_eq!(err.original(), "a\0b");
229 }
230
231 #[test]
232 fn path_element_error_whitespace_trimmed_to_empty() {
233 let err = crate::PathElementCS::new(" ").unwrap_err();
234 assert!(matches!(err.kind(), ErrorKind::Empty));
235 assert_eq!(err.original(), " ");
236 }
237
238 #[test]
239 fn path_element_error_bom_trimmed_to_dot() {
240 let err = crate::PathElementCS::new("\u{FEFF}.").unwrap_err();
241 assert!(matches!(err.kind(), ErrorKind::CurrentDirectoryMarker));
242 assert_eq!(err.original(), "\u{FEFF}.");
243 }
244}