@react-native-gpui/fs 0.0.0-stage → 0.0.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,149 @@
1
+ //! Node-compatible error mapping.
2
+ //!
3
+ //! Every failure a caller sees has to look like the one Node would have raised for
4
+ //! the same call: a `code` such as `ENOENT`, and a message in Node's format.
5
+ //! `std::io::Error` gives a kind and, on Unix, a raw errno; neither is a Node code,
6
+ //! so the translation lives here once rather than at each call site.
7
+
8
+ use rn_gpui_module::ModuleError;
9
+
10
+ /// The Node code for an `std::io::Error`.
11
+ ///
12
+ /// The raw OS error is consulted before the kind because several distinct errnos
13
+ /// share a kind: `EEXIST` and `ENOTEMPTY` are both "already exists"-ish, and
14
+ /// `EACCES` versus `EPERM` matter to a caller deciding whether to try again.
15
+ pub fn code_for(error: &std::io::Error) -> &'static str {
16
+ #[cfg(unix)]
17
+ if let Some(errno) = error.raw_os_error() {
18
+ let code = match errno {
19
+ libc::ENOENT => "ENOENT",
20
+ libc::EACCES => "EACCES",
21
+ libc::EPERM => "EPERM",
22
+ libc::EEXIST => "EEXIST",
23
+ libc::ENOTDIR => "ENOTDIR",
24
+ libc::EISDIR => "EISDIR",
25
+ libc::ENOTEMPTY => "ENOTEMPTY",
26
+ libc::EINVAL => "EINVAL",
27
+ libc::ELOOP => "ELOOP",
28
+ libc::ENOSPC => "ENOSPC",
29
+ libc::EROFS => "EROFS",
30
+ libc::EMFILE => "EMFILE",
31
+ libc::ENAMETOOLONG => "ENAMETOOLONG",
32
+ // Windows' share-violation has no Unix equivalent; callers treat it
33
+ // as a permission problem, which is what Node's `EPERM` means there.
34
+ _ => return code_for_kind(error.kind()),
35
+ };
36
+ return code;
37
+ }
38
+ // Windows exposes `raw_os_error` too, but its values are Win32 codes rather
39
+ // than errnos, so the kind is the better signal there, with the few Win32
40
+ // codes whose kind is not enough named below.
41
+ #[cfg(not(unix))]
42
+ if let Some(code) = win32_code(error.raw_os_error()) {
43
+ return code;
44
+ }
45
+ code_for_kind(error.kind())
46
+ }
47
+
48
+ /// The Node code for a Win32 error whose `ErrorKind` is too coarse.
49
+ ///
50
+ /// `ERROR_PRIVILEGE_NOT_HELD` (1314) has no kind of its own, so it would be `EIO`.
51
+ /// Creating a symbolic link without the privilege is the case that hits it, and
52
+ /// libuv, which Node is built on, reports it as `EPERM`.
53
+ #[cfg(not(unix))]
54
+ fn win32_code(raw: Option<i32>) -> Option<&'static str> {
55
+ match raw {
56
+ Some(1314) => Some("EPERM"),
57
+ _ => None,
58
+ }
59
+ }
60
+
61
+ fn code_for_kind(kind: std::io::ErrorKind) -> &'static str {
62
+ use std::io::ErrorKind;
63
+ match kind {
64
+ ErrorKind::NotFound => "ENOENT",
65
+ ErrorKind::PermissionDenied => "EACCES",
66
+ ErrorKind::AlreadyExists => "EEXIST",
67
+ ErrorKind::InvalidInput => "EINVAL",
68
+ ErrorKind::InvalidData => "EINVAL",
69
+ ErrorKind::WriteZero => "ENOSPC",
70
+ ErrorKind::StorageFull => "ENOSPC",
71
+ ErrorKind::ReadOnlyFilesystem => "EROFS",
72
+ ErrorKind::DirectoryNotEmpty => "ENOTEMPTY",
73
+ ErrorKind::IsADirectory => "EISDIR",
74
+ ErrorKind::NotADirectory => "ENOTDIR",
75
+ _ => "EIO",
76
+ }
77
+ }
78
+
79
+ /// The human half of a Node error message, by code.
80
+ ///
81
+ /// Node's own table, because the tests compare against messages and because these
82
+ /// strings are what a developer recognises when a path is wrong.
83
+ fn description_for(code: &str) -> &'static str {
84
+ match code {
85
+ "ENOENT" => "no such file or directory",
86
+ "EACCES" => "permission denied",
87
+ "EPERM" => "operation not permitted",
88
+ "EEXIST" => "file already exists",
89
+ "ENOTDIR" => "not a directory",
90
+ "EISDIR" => "illegal operation on a directory",
91
+ "ENOTEMPTY" => "directory not empty",
92
+ "EINVAL" => "invalid argument",
93
+ "ELOOP" => "too many symbolic links encountered",
94
+ "ENOSPC" => "no space left on device",
95
+ "EROFS" => "read-only file system",
96
+ "EMFILE" => "too many open files",
97
+ "ENAMETOOLONG" => "name too long",
98
+ _ => "i/o error",
99
+ }
100
+ }
101
+
102
+ /// An `std::io::Error` as the `ModuleError` JS should receive.
103
+ ///
104
+ /// `syscall` and `path` are attached by the JS layer, which knows the names the
105
+ /// caller used; the native side would have to guess between `open`, `lstat` and
106
+ /// the rest for what is already a single `stat` call.
107
+ pub fn from_io(error: std::io::Error, path: &str) -> ModuleError {
108
+ let code = code_for(&error);
109
+ ModuleError::with_code(code, format!("{code}: {}, {path}", description_for(code)))
110
+ }
111
+
112
+ /// A failure that has no `std::io::Error`, such as a closed file descriptor.
113
+ pub fn invalid(code: &str, message: impl Into<String>) -> ModuleError {
114
+ ModuleError::with_code(code, message)
115
+ }
116
+
117
+ #[cfg(test)]
118
+ mod tests {
119
+ use super::*;
120
+
121
+ #[test]
122
+ fn a_missing_file_is_enoent_with_nodes_message() {
123
+ let error = std::io::Error::new(std::io::ErrorKind::NotFound, "gone");
124
+ let mapped = from_io(error, "/nope");
125
+ assert_eq!(mapped.code.as_deref(), Some("ENOENT"));
126
+ assert_eq!(mapped.message, "ENOENT: no such file or directory, /nope");
127
+ }
128
+
129
+ #[test]
130
+ fn a_denied_file_is_eacces() {
131
+ let error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "nope");
132
+ assert_eq!(from_io(error, "/x").code.as_deref(), Some("EACCES"));
133
+ }
134
+
135
+ #[cfg(windows)]
136
+ #[test]
137
+ fn a_missing_symlink_privilege_is_eperm_not_eio() {
138
+ // ERROR_PRIVILEGE_NOT_HELD: what `symlink_dir` returns without developer
139
+ // mode, and what Node reports as EPERM.
140
+ let error = std::io::Error::from_raw_os_error(1314);
141
+ assert_eq!(from_io(error, "C:\\link").code.as_deref(), Some("EPERM"));
142
+ }
143
+
144
+ #[test]
145
+ fn an_unknown_failure_still_has_a_code() {
146
+ let error = std::io::Error::new(std::io::ErrorKind::Other, "odd");
147
+ assert_eq!(from_io(error, "/x").code.as_deref(), Some("EIO"));
148
+ }
149
+ }