aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/util/filesystem.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-08 22:14:12 +0900
committernsfisis <nsfisis@gmail.com>2026-08-08 22:14:12 +0900
commitf4cad2123b2af0de72bda4ce039e16e74f163f4e (patch)
tree21803308c5ff41e23c9d3b117433eea16b4ff663 /crates/shirabe/src/util/filesystem.rs
parent0209f63210e5b547b5c6b73367bb80ea86c255ec (diff)
downloadphp-shirabe-f4cad2123b2af0de72bda4ce039e16e74f163f4e.tar.gz
php-shirabe-f4cad2123b2af0de72bda4ce039e16e74f163f4e.tar.zst
php-shirabe-f4cad2123b2af0de72bda4ce039e16e74f163f4e.zip
feat(php-shim): give ported exceptions PHP's class hierarchy
Ported exceptions were flat structs reached with `downcast_ref`, so Composer's `catch (\RuntimeException $e)` only matched the exact leaf type and `get_class($e)` had nothing to report. Each exception now embeds an instance of the class it extends and travels inside an `AnyThrowable`; `Catch::catch`/`catch_mut` walk that chain, and `PhpClass::php_class_name` yields the PHP FQCN. Dropping the `std::error::Error` impls from the exception types leaves `AnyThrowable` as the only route into an `anyhow::Error`, so the walk cannot be bypassed. A `no_exception_downcast` linter catches the `downcast::<X>()` calls that would now silently answer `None`. Three sites change behavior as a result: the `TransportException` exit-code override reaches `MaxFileSizeExceededException`, the `catch (\LogicException)` in findSimilar() reaches its subclasses, and rendered exception titles carry the real class name rather than a guess. `get_class_err()` is no longer a `todo!()`, which re-enables FilesystemRepositoryTest::testCorruptedRepositoryFile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/util/filesystem.rs')
-rw-r--r--crates/shirabe/src/util/filesystem.rs98
1 files changed, 42 insertions, 56 deletions
diff --git a/crates/shirabe/src/util/filesystem.rs b/crates/shirabe/src/util/filesystem.rs
index daf26e22..7010fd86 100644
--- a/crates/shirabe/src/util/filesystem.rs
+++ b/crates/shirabe/src/util/filesystem.rs
@@ -124,9 +124,11 @@ impl Filesystem {
// `rm -rf`/`rmdir` subprocess via the String-based ProcessExecutor, so it has to be
// representable as UTF-8.
let directory = directory.as_ref();
- let directory = directory.to_str().ok_or_else(|| RuntimeException {
- message: format!("Path contains invalid UTF-8: {}", directory.display()),
- code: 0,
+ let directory = directory.to_str().ok_or_else(|| {
+ RuntimeException::new(format!(
+ "Path contains invalid UTF-8: {}",
+ directory.display()
+ ))
})?;
let edge_case_result = self.remove_edge_cases(directory, true)?;
if let Some(r) = edge_case_result {
@@ -246,10 +248,7 @@ impl Filesystem {
}
if Preg::is_match3(php_regex!("{^(?:[a-z]:)?[/\\\\]+$}i"), directory, None) {
- return Err(RuntimeException {
- message: format!("Aborting an attempted deletion of {}, this was probably not intended, if it is a real use case please report it.", directory),
- code: 0,
- }
+ return Err(RuntimeException::new(format!("Aborting an attempted deletion of {}, this was probably not intended, if it is a real use case please report it.", directory))
.into());
}
@@ -309,42 +308,36 @@ impl Filesystem {
pub fn ensure_directory_exists(&mut self, directory: &str) -> anyhow::Result<()> {
if !is_dir(directory) {
if file_exists(directory) {
- return Err(RuntimeException {
- message: format!("{} exists and is not a directory.", directory),
- code: 0,
- }
+ return Err(RuntimeException::new(format!(
+ "{} exists and is not a directory.",
+ directory
+ ))
.into());
}
if is_link(directory) && !self.unlink_implementation(Path::new(directory)) {
- return Err(RuntimeException {
- message: format!(
- "Could not delete symbolic link {}: {}",
- directory,
- error_get_last()
- .as_ref()
- .and_then(|m| m.get("message"))
- .and_then(|v| v.as_string())
- .unwrap_or("")
- ),
- code: 0,
- }
+ return Err(RuntimeException::new(format!(
+ "Could not delete symbolic link {}: {}",
+ directory,
+ error_get_last()
+ .as_ref()
+ .and_then(|m| m.get("message"))
+ .and_then(|v| v.as_string())
+ .unwrap_or("")
+ ))
.into());
}
if !mkdir(directory, 0o777, true) {
- let e = RuntimeException {
- message: format!(
- "{} does not exist and could not be created: {}",
- directory,
- error_get_last()
- .as_ref()
- .and_then(|m| m.get("message"))
- .and_then(|v| v.as_string())
- .unwrap_or("")
- ),
- code: 0,
- };
+ let e = RuntimeException::new(format!(
+ "{} does not exist and could not be created: {}",
+ directory,
+ error_get_last()
+ .as_ref()
+ .and_then(|m| m.get("message"))
+ .and_then(|v| v.as_string())
+ .unwrap_or("")
+ ));
// in pathological cases with paths like path/to/broken-symlink/../foo is_dir will fail to detect path/to/foo
// but normalizing the ../ away first makes it work so we attempt this just in case, and if it still fails we
@@ -390,7 +383,7 @@ impl Filesystem {
message.push_str("\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed");
}
- return Err(RuntimeException { message, code: 0 }.into());
+ return Err(RuntimeException::new(message).into());
}
}
@@ -423,7 +416,7 @@ impl Filesystem {
message.push_str("\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed");
}
- return Err(RuntimeException { message, code: 0 }.into());
+ return Err(RuntimeException::new(message).into());
}
}
@@ -464,7 +457,7 @@ impl Filesystem {
// if copy fails we attempt to copy it manually as this can help bypass issues with VirtualBox shared folders
// see https://github.com/composer/composer/issues/12057
- if str_contains(&e.message, "Bad address") {
+ if str_contains(e.get_message(), "Bad address") {
let (source_handle, target_handle) =
match (fopen(source, "r"), fopen(&target, "w")) {
(Ok(source_handle), Ok(target_handle)) => {
@@ -520,13 +513,11 @@ impl Filesystem {
// TODO(phase-c):
// The fallbacks below (copy_then_remove and the mv/xcopy subprocesses) operate on
// path strings, so beyond this point the paths have to be representable as UTF-8.
- let source = source.to_str().ok_or_else(|| RuntimeException {
- message: format!("Path contains invalid UTF-8: {}", source.display()),
- code: 0,
+ let source = source.to_str().ok_or_else(|| {
+ RuntimeException::new(format!("Path contains invalid UTF-8: {}", source.display()))
})?;
- let target = target.to_str().ok_or_else(|| RuntimeException {
- message: format!("Path contains invalid UTF-8: {}", target.display()),
- code: 0,
+ let target = target.to_str().ok_or_else(|| {
+ RuntimeException::new(format!("Path contains invalid UTF-8: {}", target.display()))
})?;
if !function_exists("proc_open") {
@@ -735,11 +726,9 @@ impl Filesystem {
pub fn size(&self, path: impl AsRef<Path>) -> anyhow::Result<i64> {
let path = path.as_ref();
if !file_exists(path) {
- return Err(RuntimeException {
- message: format!("{} does not exist.", path.display()),
- code: 0,
- }
- .into());
+ return Err(
+ RuntimeException::new(format!("{} does not exist.", path.display())).into(),
+ );
}
if is_dir(path) {
return self.directory_size(path);
@@ -987,13 +976,10 @@ impl Filesystem {
/// Creates an NTFS junction.
pub fn junction(&mut self, target: &str, junction: &str) -> anyhow::Result<()> {
if !Platform::is_windows() {
- return Err(LogicException {
- message: format!(
- "Function {} is not available on non-Windows platform",
- "Composer\\Util\\Filesystem"
- ),
- code: 0,
- }
+ return Err(LogicException::new(format!(
+ "Function {} is not available on non-Windows platform",
+ "Composer\\Util\\Filesystem"
+ ))
.into());
}
if !is_dir(target) {