diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-08 22:14:12 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-08 22:14:12 +0900 |
| commit | f4cad2123b2af0de72bda4ce039e16e74f163f4e (patch) | |
| tree | 21803308c5ff41e23c9d3b117433eea16b4ff663 /crates/shirabe/src/downloader | |
| parent | 0209f63210e5b547b5c6b73367bb80ea86c255ec (diff) | |
| download | php-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/downloader')
| -rw-r--r-- | crates/shirabe/src/downloader/archive_downloader.rs | 16 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/download_manager.rs | 71 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/file_downloader.rs | 54 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/filesystem_exception.rs | 18 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/fossil_downloader.rs | 24 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/git_downloader.rs | 188 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/gzip_downloader.rs | 5 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/hg_downloader.rs | 44 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/max_file_size_exceeded_exception.rs | 14 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/path_downloader.rs | 100 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/rar_downloader.rs | 27 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/svn_downloader.rs | 73 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/transport_exception.rs | 34 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/vcs_downloader.rs | 78 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/zip_downloader.rs | 84 |
15 files changed, 334 insertions, 496 deletions
diff --git a/crates/shirabe/src/downloader/archive_downloader.rs b/crates/shirabe/src/downloader/archive_downloader.rs index 6ddf29b6..d2ee4a75 100644 --- a/crates/shirabe/src/downloader/archive_downloader.rs +++ b/crates/shirabe/src/downloader/archive_downloader.rs @@ -9,6 +9,7 @@ use crate::util::Filesystem; use crate::util::Platform; use indexmap::IndexMap; use shirabe_external_packages::symfony::finder::Finder; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ DIRECTORY_SEPARATOR, PhpMixed, RuntimeException, bin2hex, file_exists, is_dir, random_bytes, realpath, @@ -155,7 +156,7 @@ pub trait ArchiveDownloader { Ok(false) => {} Err(e) => { // ignore error, and simply do not renameAsOne - if e.downcast_ref::<RuntimeException>().is_none() { + if !e.is_instanceof::<RuntimeException>() { return Err(e); } } @@ -273,14 +274,11 @@ fn rename_recursively( ); if is_dir(&target) { if !is_dir(file) { - return Err(RuntimeException { - message: format!( - "Installing {} would lead to overwriting the {} directory with a file from the package, invalid operation.", - package, - target.display() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Installing {} would lead to overwriting the {} directory with a file from the package, invalid operation.", + package, + target.display() + )) .into()); } rename_recursively(filesystem, package.clone(), file, &target)?; diff --git a/crates/shirabe/src/downloader/download_manager.rs b/crates/shirabe/src/downloader/download_manager.rs index 423eda28..8c10de5d 100644 --- a/crates/shirabe/src/downloader/download_manager.rs +++ b/crates/shirabe/src/downloader/download_manager.rs @@ -9,6 +9,7 @@ use crate::package::PackageInterfaceHandle; use crate::util::Filesystem; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ InvalidArgumentException, LogicException, PhpMixed, RuntimeException, array_keys, array_reverse, array_shift, dirname, implode, in_array_strict, preg_quote, rtrim, str_replace, @@ -99,14 +100,11 @@ impl DownloadManager { ) -> anyhow::Result<std::rc::Rc<std::cell::RefCell<dyn DownloaderInterface>>> { let r#type = strtolower(r#type); if !self.downloaders.contains_key(&r#type) { - return Err(InvalidArgumentException { - message: format!( - "Unknown downloader type: {}. Available types: {}.", - r#type, - implode(", ", &array_keys(&self.downloaders)), - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Unknown downloader type: {}. Available types: {}.", + r#type, + implode(", ", &array_keys(&self.downloaders)), + )) .into()); } @@ -134,28 +132,22 @@ impl DownloadManager { } else if installation_source.as_deref() == Some("source") { self.get_downloader(&package.get_source_type().unwrap_or_default())? } else { - return Err(InvalidArgumentException { - message: format!( - "Package {} does not have an installation source set", - package, - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package {} does not have an installation source set", + package, + )) .into()); }; let downloader_installation_source = downloader.borrow().get_installation_source(); if installation_source.as_deref() != Some(&downloader_installation_source) { - return Err(LogicException { - message: format!( - "Downloader \"{}\" is a {} type downloader and can not be used to download {} for package {}", - downloader.borrow().php_class_name(), - downloader_installation_source, - installation_source.unwrap_or_default(), - package, - ), - code: 0, - } + return Err(LogicException::new(format!( + "Downloader \"{}\" is a {} type downloader and can not be used to download {} for package {}", + downloader.borrow().php_class_name(), + downloader_installation_source, + installation_source.unwrap_or_default(), + package, + )) .into()); } @@ -227,19 +219,18 @@ impl DownloadManager { { Ok(r) => r, Err(e) => { - let is_runtime = e.downcast_ref::<RuntimeException>().is_some(); - let is_irrecoverable = - e.downcast_ref::<IrrecoverableDownloadException>().is_some(); - if is_runtime && !is_irrecoverable { + if e.is_instanceof::<RuntimeException>() + && !e.is_instanceof::<IrrecoverableDownloadException>() + { if sources.is_empty() { return Err(e); } let message = e - .downcast_ref::<RuntimeException>() + .catch::<RuntimeException>() .unwrap() - .message - .clone(); + .get_message() + .to_string(); self.io.write_error3( &format!( " <warning>Failed to download {} from {}: {}</warning>", @@ -352,17 +343,17 @@ impl DownloadManager { Ok(p) => return Ok(p), Err(e) => { // PHP catches only \RuntimeException; other exceptions propagate uncaught. - if e.downcast_ref::<RuntimeException>().is_none() { + if !e.is_instanceof::<RuntimeException>() { return Err(e); } if !self.io.is_interactive() { return Err(e); } let message = e - .downcast_ref::<RuntimeException>() + .catch::<RuntimeException>() .unwrap() - .message - .clone(); + .get_message() + .to_string(); self.io.write_error3( &format!("<error> Update failed ({})</error>", message), true, @@ -477,10 +468,10 @@ impl DownloadManager { } if sources.is_empty() { - return Err(InvalidArgumentException { - message: format!("Package {} must have a source or dist specified", package), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package {} must have a source or dist specified", + package + )) .into()); } diff --git a/crates/shirabe/src/downloader/file_downloader.rs b/crates/shirabe/src/downloader/file_downloader.rs index cdcf750e..9b86ee56 100644 --- a/crates/shirabe/src/downloader/file_downloader.rs +++ b/crates/shirabe/src/downloader/file_downloader.rs @@ -25,6 +25,7 @@ use crate::util::Silencer; use crate::util::Url as UrlUtil; use crate::util::sync_executor; use indexmap::IndexMap; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ DIRECTORY_SEPARATOR, InvalidArgumentException, PATHINFO_BASENAME, PATHINFO_EXTENSION, PHP_URL_PATH, PhpMixed, RuntimeException, UnexpectedValueException, array_search, file_exists, @@ -359,11 +360,9 @@ impl FileDownloader { url: &str, ) -> anyhow::Result<String> { if !shirabe_php_shim::extension_loaded("openssl") && Some(0) == strpos(url, "https:") { - return Err(RuntimeException { - message: "You must enable the openssl extension to download files via https" - .to_string(), - code: 0, - } + return Err(RuntimeException::new( + "You must enable the openssl extension to download files via https".to_string(), + ) .into()); } @@ -400,10 +399,9 @@ impl DownloaderInterface for FileDownloader { output: bool, ) -> anyhow::Result<Option<PhpMixed>> { if package.get_dist_url().is_none() { - return Err(InvalidArgumentException { - message: "The given package is missing url information".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "The given package is missing url information".to_string(), + ) .into()); } @@ -558,15 +556,15 @@ impl DownloaderInterface for FileDownloader { } self.clear_last_cache_write(package.clone()); - if e.downcast_ref::<IrrecoverableDownloadException>().is_some() { + if e.is_instanceof::<IrrecoverableDownloadException>() { return Err(e); } - if e.downcast_ref::<MaxFileSizeExceededException>().is_some() { + if e.is_instanceof::<MaxFileSizeExceededException>() { return Err(e); } - if let Some(te) = e.downcast_ref::<TransportException>() { + if let Some(te) = e.catch::<TransportException>() { // if we got an http response with a proper code, then requesting again will probably not help, abort if 0 != te.get_code() && !matches!(te.get_code(), 500 | 502 | 503 | 504) { @@ -592,7 +590,7 @@ impl DownloaderInterface for FileDownloader { } if !urls.is_empty() { let code = e - .downcast_ref::<TransportException>() + .catch::<TransportException>() .map_or(0, |te| te.get_code()); if self.io.borrow().is_debug() { self.io.borrow().write_error(&format!( @@ -628,13 +626,10 @@ impl DownloaderInterface for FileDownloader { // === $result->then(verify) === if !file_exists(&file_name) { - return Err(UnexpectedValueException { - message: format!( - "{} could not be saved to {}, make sure the directory is writable and you have internet connectivity", - url.base, file_name - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "{} could not be saved to {}, make sure the directory is writable and you have internet connectivity", + url.base, file_name + )) .into()); } @@ -642,13 +637,10 @@ impl DownloaderInterface for FileDownloader { && !checksum.is_empty() && hash_file("sha1", &file_name).as_deref() != Some(checksum) { - return Err(UnexpectedValueException { - message: format!( - "The checksum verification of the file failed (downloaded from {})", - url.base - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "The checksum verification of the file failed (downloaded from {})", + url.base + )) .into()); } @@ -809,10 +801,10 @@ impl DownloaderInterface for FileDownloader { } let result = Filesystem::remove_directory_async_via(&self.filesystem, path).await?; if !result { - return Err(RuntimeException { - message: format!("Could not completely delete {}, aborting.", path), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not completely delete {}, aborting.", + path + )) .into()); } diff --git a/crates/shirabe/src/downloader/filesystem_exception.rs b/crates/shirabe/src/downloader/filesystem_exception.rs index f0aa831f..6e90e328 100644 --- a/crates/shirabe/src/downloader/filesystem_exception.rs +++ b/crates/shirabe/src/downloader/filesystem_exception.rs @@ -7,17 +7,15 @@ pub struct FilesystemException(pub Exception); impl FilesystemException { pub fn new(message: String, code: i64) -> Self { - FilesystemException(Exception { - message: format!("Filesystem exception: \n{}", message), + FilesystemException(Exception::with_code( + format!("Filesystem exception: \n{}", message), code, - }) + )) } } -impl std::fmt::Display for FilesystemException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl std::error::Error for FilesystemException {} +shirabe_php_shim::impl_php_exception!( + FilesystemException, + 0, + r"Composer\Downloader\FilesystemException" +); diff --git a/crates/shirabe/src/downloader/fossil_downloader.rs b/crates/shirabe/src/downloader/fossil_downloader.rs index 72409a1f..86a44f0b 100644 --- a/crates/shirabe/src/downloader/fossil_downloader.rs +++ b/crates/shirabe/src/downloader/fossil_downloader.rs @@ -47,14 +47,11 @@ impl FossilDownloader { .execute(&command, output, cwd.as_deref())? != 0 { - return Err(RuntimeException { - message: format!( - "Failed to execute {}\n\n{}", - command.join(" "), - self.inner.process.borrow().get_error_output() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to execute {}\n\n{}", + command.join(" "), + self.inner.process.borrow().get_error_output() + )) .into()); } Ok(()) @@ -168,13 +165,10 @@ impl VcsDownloader for FossilDownloader { )); if !self.has_metadata_repository(path) { - return Err(RuntimeException { - message: format!( - "The .fslckout file is missing from {}, see https://getcomposer.org/commit-deps for more information", - path - ), - code: 0, - }.into()); + return Err(RuntimeException::new(format!( + "The .fslckout file is missing from {}, see https://getcomposer.org/commit-deps for more information", + path + )).into()); } let real_path = shirabe_php_shim::realpath(path); diff --git a/crates/shirabe/src/downloader/git_downloader.rs b/crates/shirabe/src/downloader/git_downloader.rs index 275050fd..0a32d2fd 100644 --- a/crates/shirabe/src/downloader/git_downloader.rs +++ b/crates/shirabe/src/downloader/git_downloader.rs @@ -86,14 +86,11 @@ impl GitDownloader { .execute_args(&command, &mut output, Some(&path)) != 0 { - return Err(RuntimeException { - message: format!( - "Failed to execute {}\n\n{}", - implode(" ", &command), - self.inner.process.borrow().get_error_output(), - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to execute {}\n\n{}", + implode(" ", &command), + self.inner.process.borrow().get_error_output(), + )) .into()); } @@ -186,14 +183,11 @@ impl GitDownloader { Some(&path), ) != 0 { - return Err(RuntimeException { - message: format!( - "Failed to execute {}\n\n{}", - implode(" ", &command), - self.inner.process.borrow().get_error_output(), - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to execute {}\n\n{}", + implode(" ", &command), + self.inner.process.borrow().get_error_output(), + )) .into()); } @@ -232,14 +226,11 @@ impl GitDownloader { .execute_args(&command, &mut output, Some(&path)) != 0 { - return Err(RuntimeException { - message: format!( - "Failed to execute {}\n\n{}", - implode(" ", &command), - self.inner.process.borrow().get_error_output(), - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to execute {}\n\n{}", + implode(" ", &command), + self.inner.process.borrow().get_error_output(), + )) .into()); } refs = trim(&output, None); @@ -486,15 +477,12 @@ impl GitDownloader { let command = format!("{} && {}", implode(" ", &command1), implode(" ", &command2)); - Err(RuntimeException { - message: Url::sanitize(format!( - "Failed to execute {}\n\n{}{}", - command, - self.inner.process.borrow().get_error_output(), - exception_extra, - )), - code: 0, - } + Err(RuntimeException::new(Url::sanitize(format!( + "Failed to execute {}\n\n{}{}", + command, + self.inner.process.borrow().get_error_output(), + exception_extra, + ))) .into()) } @@ -570,11 +558,9 @@ impl GitDownloader { Some(&path), ) != 0 { - return Err(RuntimeException { - message: format!("Could not reset changes\n\n:{}", output), - code: 0, - } - .into()); + return Err( + RuntimeException::new(format!("Could not reset changes\n\n:{}", output)).into(), + ); } let mut output = String::new(); if self.inner.process.borrow_mut().execute_args( @@ -583,11 +569,9 @@ impl GitDownloader { Some(&path), ) != 0 { - return Err(RuntimeException { - message: format!("Could not reset changes\n\n:{}", output), - code: 0, - } - .into()); + return Err( + RuntimeException::new(format!("Could not reset changes\n\n:{}", output)).into(), + ); } self.has_discarded_changes.borrow_mut().insert(path, true); @@ -609,11 +593,9 @@ impl GitDownloader { Some(&path), ) != 0 { - return Err(RuntimeException { - message: format!("Could not stash changes\n\n:{}", output), - code: 0, - } - .into()); + return Err( + RuntimeException::new(format!("Could not stash changes\n\n:{}", output)).into(), + ); } self.has_stashed_changes.borrow_mut().insert(path, true); @@ -631,11 +613,9 @@ impl GitDownloader { Some(&path), ) != 0 { - return Err(RuntimeException { - message: format!("Could not view diff\n\n:{}", output), - code: 0, - } - .into()); + return Err( + RuntimeException::new(format!("Could not view diff\n\n:{}", output)).into(), + ); } self.inner @@ -692,10 +672,10 @@ impl GitDownloader { path: &str, ) -> anyhow::Result<()> { if self.get_local_changes(package, path)?.is_some() { - return Err(RuntimeException { - message: format!("Source directory {} has uncommitted changes.", path), - code: 0, - } + return Err(RuntimeException::new(format!( + "Source directory {} has uncommitted changes.", + path + )) .into()); } @@ -738,14 +718,11 @@ impl ChangeReportInterface for GitDownloader { .execute_args(&command, &mut output, Some(path)) != 0 { - return Err(RuntimeException { - message: format!( - "Failed to execute {}\n\n{}", - implode(" ", &command), - self.inner.process.borrow().get_error_output(), - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to execute {}\n\n{}", + implode(" ", &command), + self.inner.process.borrow().get_error_output(), + )) .into()); } @@ -848,10 +825,9 @@ impl VcsDownloader for GitDownloader { .insert(r#ref.as_deref().unwrap_or("").to_string(), true); } } else if git_version.is_none() { - return Err(RuntimeException { - message: "git was not found in your PATH, skipping source download".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "git was not found in your PATH, skipping source download".to_string(), + ) .into()); } @@ -975,13 +951,10 @@ impl VcsDownloader for GitDownloader { ], ]; if Platform::get_env("COMPOSER_DISABLE_NETWORK").is_some() { - return Err(RuntimeException { - message: format!( - "The required git reference for {} is not in cache and network is disabled, aborting", - package.get_name(), - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "The required git reference for {} is not in cache and network is disabled, aborting", + package.get_name(), + )) .into()); } } @@ -1022,13 +995,10 @@ impl VcsDownloader for GitDownloader { GitUtil::clean_env(&self.inner.process); let path = self.normalize_path(path); if !self.has_metadata_repository(&path) { - return Err(RuntimeException { - message: format!( - "The .git directory is missing from {}, see https://getcomposer.org/commit-deps for more information", - path - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "The .git directory is missing from {}, see https://getcomposer.org/commit-deps for more information", + path + )) .into()); } @@ -1060,13 +1030,10 @@ impl VcsDownloader for GitDownloader { msg = format!("Checking out {}", self.get_short_hash(&r#ref)); remote_url = "%url%".to_string(); if Platform::get_env("COMPOSER_DISABLE_NETWORK").is_some() { - return Err(RuntimeException { - message: format!( - "The required git reference for {} is not in cache and network is disabled, aborting", - target.get_name(), - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "The required git reference for {} is not in cache and network is disabled, aborting", + target.get_name(), + )) .into()); } } @@ -1196,13 +1163,10 @@ impl VcsDownloader for GitDownloader { .as_bool() != Some(true)) { - return Err(RuntimeException { - message: format!( - "Source directory {} has unpushed changes on the current branch: \n{}", - path, unpushed - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Source directory {} has unpushed changes on the current branch: \n{}", + path, unpushed + )) .into()); } @@ -1285,11 +1249,7 @@ impl VcsDownloader for GitDownloader { } } Some("n") => { - return Err(RuntimeException { - message: "Update aborted".to_string(), - code: 0, - } - .into()); + return Err(RuntimeException::new("Update aborted".to_string()).into()); } Some("v") => { self.inner @@ -1362,13 +1322,10 @@ impl VcsDownloader for GitDownloader { Some(&path), ) != 0 { - return Err(RuntimeException { - message: format!( - "Failed to apply stashed changes:\n\n{}", - self.inner.process.borrow().get_error_output() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to apply stashed changes:\n\n{}", + self.inner.process.borrow().get_error_output() + )) .into()); } } @@ -1399,14 +1356,11 @@ impl VcsDownloader for GitDownloader { .execute_args(&command, &mut output, Some(&path)) != 0 { - return Err(RuntimeException { - message: format!( - "Failed to execute {}\n\n{}", - implode(" ", &command), - self.inner.process.borrow().get_error_output(), - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to execute {}\n\n{}", + implode(" ", &command), + self.inner.process.borrow().get_error_output(), + )) .into()); } diff --git a/crates/shirabe/src/downloader/gzip_downloader.rs b/crates/shirabe/src/downloader/gzip_downloader.rs index b0494479..9473deff 100644 --- a/crates/shirabe/src/downloader/gzip_downloader.rs +++ b/crates/shirabe/src/downloader/gzip_downloader.rs @@ -127,10 +127,7 @@ impl ArchiveDownloader for GzipDownloader { implode(" ", &command), self.inner.process.borrow().get_error_output(), ); - return Err(anyhow::anyhow!(RuntimeException { - message: process_error, - code: 0 - })); + return Err(RuntimeException::new(process_error).into()); } self.extract_using_ext(file, &target_filepath); diff --git a/crates/shirabe/src/downloader/hg_downloader.rs b/crates/shirabe/src/downloader/hg_downloader.rs index e555ccf5..dfd25618 100644 --- a/crates/shirabe/src/downloader/hg_downloader.rs +++ b/crates/shirabe/src/downloader/hg_downloader.rs @@ -64,10 +64,9 @@ impl VcsDownloader for HgDownloader { _prev_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<Option<PhpMixed>> { if HgUtils::get_version(&self.inner.process).is_none() { - return Err(RuntimeException { - message: "hg was not found in your PATH, skipping source download".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "hg was not found in your PATH, skipping source download".to_string(), + ) .into()); } @@ -111,14 +110,11 @@ impl VcsDownloader for HgDownloader { shirabe_php_shim::realpath(path).as_deref(), ) != 0 { - return Err(RuntimeException { - message: format!( - "Failed to execute {}\n\n{}", - command.join(" "), - self.inner.process.borrow().get_error_output() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to execute {}\n\n{}", + command.join(" "), + self.inner.process.borrow().get_error_output() + )) .into()); } @@ -145,13 +141,10 @@ impl VcsDownloader for HgDownloader { )); if !self.has_metadata_repository(path) { - return Err(RuntimeException { - message: format!( - "The .hg directory is missing from {}, see https://getcomposer.org/commit-deps for more information", - path - ), - code: 0, - }.into()); + return Err(RuntimeException::new(format!( + "The .hg directory is missing from {}, see https://getcomposer.org/commit-deps for more information", + path + )).into()); } let pull_command = |url: String| -> Vec<String> { @@ -195,14 +188,11 @@ impl VcsDownloader for HgDownloader { shirabe_php_shim::realpath(path).as_deref(), ) != 0 { - return Err(RuntimeException { - message: format!( - "Failed to execute {}\n\n{}", - command.join(" "), - self.inner.process.borrow().get_error_output() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to execute {}\n\n{}", + command.join(" "), + self.inner.process.borrow().get_error_output() + )) .into()); } diff --git a/crates/shirabe/src/downloader/max_file_size_exceeded_exception.rs b/crates/shirabe/src/downloader/max_file_size_exceeded_exception.rs index ebec74df..7a730ff2 100644 --- a/crates/shirabe/src/downloader/max_file_size_exceeded_exception.rs +++ b/crates/shirabe/src/downloader/max_file_size_exceeded_exception.rs @@ -1,6 +1,6 @@ //! ref: composer/src/Composer/Downloader/MaxFileSizeExceededException.php -use crate::downloader::TransportException; +use crate::downloader::transport_exception::TransportException; #[derive(Debug)] pub struct MaxFileSizeExceededException(pub TransportException); @@ -11,10 +11,8 @@ impl MaxFileSizeExceededException { } } -impl std::fmt::Display for MaxFileSizeExceededException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl std::error::Error for MaxFileSizeExceededException {} +shirabe_php_shim::impl_php_exception!( + MaxFileSizeExceededException, + 0, + r"Composer\Downloader\MaxFileSizeExceededException" +); diff --git a/crates/shirabe/src/downloader/path_downloader.rs b/crates/shirabe/src/downloader/path_downloader.rs index f10da72a..9449e74a 100644 --- a/crates/shirabe/src/downloader/path_downloader.rs +++ b/crates/shirabe/src/downloader/path_downloader.rs @@ -85,17 +85,14 @@ impl PathDownloader { package: PackageInterfaceHandle, path: &str, ) -> anyhow::Result<String> { - let url = package.get_dist_url().ok_or_else(|| RuntimeException { - message: format!( + let url = package.get_dist_url().ok_or_else(|| { + RuntimeException::new(format!( "The package {} has no dist url configured, cannot install.", package.get_pretty_name() - ), - code: 0, - })?; - let real_url = realpath(&url).ok_or_else(|| RuntimeException { - message: format!("Failed to realpath {}", url), - code: 0, + )) })?; + let real_url = realpath(&url) + .ok_or_else(|| RuntimeException::new(format!("Failed to realpath {}", url)))?; if realpath(path).as_deref() == Some(&real_url) { return Ok(": Source already present".to_string()); @@ -157,10 +154,7 @@ impl PathDownloader { && !self.safe_junctions() { if !allowed_strategies.contains(&Self::STRATEGY_MIRROR) { - return Err(RuntimeException { - message: "You are on an old Windows / old PHP combo which does not allow Composer to use junctions/symlinks and this path repository has symlink:true in its options so copying is not allowed".to_string(), - code: 0, - } + return Err(RuntimeException::new("You are on an old Windows / old PHP combo which does not allow Composer to use junctions/symlinks and this path repository has symlink:true in its options so copying is not allowed".to_string()) .into()); } current_strategy = Self::STRATEGY_MIRROR; @@ -173,10 +167,7 @@ impl PathDownloader { && !function_exists("symlink") { if !allowed_strategies.contains(&Self::STRATEGY_MIRROR) { - return Err(RuntimeException { - message: "Your PHP has the symlink() function disabled which does not allow Composer to use symlinks and this path repository has symlink:true in its options so copying is not allowed".to_string(), - code: 0, - } + return Err(RuntimeException::new("Your PHP has the symlink() function disabled which does not allow Composer to use symlinks and this path repository has symlink:true in its options so copying is not allowed".to_string()) .into()); } current_strategy = Self::STRATEGY_MIRROR; @@ -243,26 +234,22 @@ impl DownloaderInterface for PathDownloader { output: bool, ) -> anyhow::Result<Option<PhpMixed>> { let path = Filesystem::trim_trailing_slash(path); - let url = package.get_dist_url().ok_or_else(|| RuntimeException { - message: format!( + let url = package.get_dist_url().ok_or_else(|| { + RuntimeException::new(format!( "The package {} has no dist url configured, cannot download.", package.get_pretty_name() - ), - code: 0, + )) })?; let real_url = realpath(&url); if real_url.is_none() || !file_exists(real_url.as_deref().unwrap_or("")) || !is_dir(real_url.as_deref().unwrap_or("")) { - return Err(RuntimeException { - message: format!( - "Source path \"{}\" is not found for package {}", - url, - package.get_name() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Source path \"{}\" is not found for package {}", + url, + package.get_name() + )) .into()); } let real_url = real_url.unwrap(); @@ -282,15 +269,12 @@ impl DownloaderInterface for PathDownloader { // // Please see https://github.com/composer/composer/pull/5974 and https://github.com/composer/composer/pull/6174 // for previous attempts that were shut down because they did not work well enough or introduced too many risks. - return Err(RuntimeException { - message: format!( - "Package {} cannot install to \"{}\" inside its source at \"{}\"", - package.get_name(), - realpath(&path).unwrap_or_default(), - real_url - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Package {} cannot install to \"{}\" inside its source at \"{}\"", + package.get_name(), + realpath(&path).unwrap_or_default(), + real_url + )) .into()); } @@ -316,17 +300,14 @@ impl DownloaderInterface for PathDownloader { output: bool, ) -> anyhow::Result<Option<PhpMixed>> { let path = Filesystem::trim_trailing_slash(path); - let url = package.get_dist_url().ok_or_else(|| RuntimeException { - message: format!( + let url = package.get_dist_url().ok_or_else(|| { + RuntimeException::new(format!( "The package {} has no dist url configured, cannot install.", package.get_pretty_name() - ), - code: 0, - })?; - let real_url = realpath(&url).ok_or_else(|| RuntimeException { - message: format!("Failed to realpath {}", url), - code: 0, + )) })?; + let real_url = realpath(&url) + .ok_or_else(|| RuntimeException::new(format!("Failed to realpath {}", url)))?; if realpath(&path).as_deref() == Some(&real_url) { if output { @@ -442,13 +423,10 @@ impl DownloaderInterface for PathDownloader { current_strategy = Self::STRATEGY_MIRROR; is_fallback = true; } else { - return Err(RuntimeException { - message: format!( - "Symlink from \"{}\" to \"{}\" failed!", - real_url, path - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Symlink from \"{}\" to \"{}\" failed!", + real_url, path + )) .into()); } } @@ -537,25 +515,21 @@ impl DownloaderInterface for PathDownloader { true, io_interface::NORMAL, ); - return Err(RuntimeException { - message: format!( - "Could not reliably remove junction for package {}", - package.get_name() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not reliably remove junction for package {}", + package.get_name() + )) .into()); } return Ok(None); } - let url = package.get_dist_url().ok_or_else(|| RuntimeException { - message: format!( + let url = package.get_dist_url().ok_or_else(|| { + RuntimeException::new(format!( "The package {} has no dist url configured, cannot remove.", package.get_pretty_name() - ), - code: 0, + )) })?; // ensure that the source path (dist url) is not the same as the install path, which diff --git a/crates/shirabe/src/downloader/rar_downloader.rs b/crates/shirabe/src/downloader/rar_downloader.rs index ee0148c7..f4f601bc 100644 --- a/crates/shirabe/src/downloader/rar_downloader.rs +++ b/crates/shirabe/src/downloader/rar_downloader.rs @@ -114,39 +114,30 @@ impl ArchiveDownloader for RarDownloader { process_error.as_deref().unwrap_or(""), ) }; - return Err(RuntimeException { - message: error, - code: 0, - } - .into()); + return Err(RuntimeException::new(error).into()); } let rar_archive = RarArchive::open(file); if rar_archive.is_none() { - return Err(UnexpectedValueException { - message: format!("Could not open RAR archive: {}", file), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Could not open RAR archive: {}", + file + )) .into()); } let rar_archive = rar_archive.unwrap(); let entries = rar_archive.get_entries(); if entries.is_none() { - return Err(RuntimeException { - message: "Could not retrieve RAR archive entries".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "Could not retrieve RAR archive entries".to_string(), + ) .into()); } for entry in entries.unwrap() { if !entry.extract(path) { - return Err(RuntimeException { - message: "Could not extract entry".to_string(), - code: 0, - } - .into()); + return Err(RuntimeException::new("Could not extract entry".to_string()).into()); } } diff --git a/crates/shirabe/src/downloader/svn_downloader.rs b/crates/shirabe/src/downloader/svn_downloader.rs index 6a8bdf0e..cb748a93 100644 --- a/crates/shirabe/src/downloader/svn_downloader.rs +++ b/crates/shirabe/src/downloader/svn_downloader.rs @@ -75,13 +75,10 @@ impl SvnDownloader { Some(path), ) != 0 { - return Err(RuntimeException { - message: format!( - "Could not reset changes\n\n:{}", - self.inner.process.borrow().get_error_output() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not reset changes\n\n:{}", + self.inner.process.borrow().get_error_output() + )) .into()); } @@ -96,10 +93,10 @@ impl SvnDownloader { path: &str, ) -> anyhow::Result<()> { if self.get_local_changes(package, path)?.is_some() { - return Err(RuntimeException { - message: format!("Source directory {} has uncommitted changes.", path), - code: 0, - } + return Err(RuntimeException::new(format!( + "Source directory {} has uncommitted changes.", + path + )) .into()); } @@ -143,10 +140,9 @@ impl VcsDownloader for SvnDownloader { Some(self.inner.process.clone()), ); if util.binary_version().is_none() { - return Err(RuntimeException { - message: "svn was not found in your PATH, skipping source download".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "svn was not found in your PATH, skipping source download".to_string(), + ) .into()); } @@ -206,13 +202,10 @@ impl VcsDownloader for SvnDownloader { let r#ref = target.get_source_reference(); if !self.has_metadata_repository(path) { - return Err(RuntimeException { - message: format!( - "The .svn directory is missing from {}, see https://getcomposer.org/commit-deps for more information", - path - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "The .svn directory is missing from {}, see https://getcomposer.org/commit-deps for more information", + path + )) .into()); } @@ -324,11 +317,7 @@ impl VcsDownloader for SvnDownloader { break; } Some("n") => { - return Err(RuntimeException { - message: "Update aborted".to_string(), - code: 0, - } - .into()); + return Err(RuntimeException::new("Update aborted".to_string()).into()); } Some("v") => { for line in &changes { @@ -384,14 +373,11 @@ impl VcsDownloader for SvnDownloader { .execute_args(&command, &mut output, Some(path)) != 0 { - return Err(RuntimeException { - message: format!( - "Failed to execute {}\n\n{}", - command.join(" "), - self.inner.process.borrow().get_error_output() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to execute {}\n\n{}", + command.join(" "), + self.inner.process.borrow().get_error_output() + )) .into()); } @@ -403,10 +389,10 @@ impl VcsDownloader for SvnDownloader { .cloned() .unwrap_or_default() } else { - return Err(RuntimeException { - message: format!("Unable to determine svn url for path {}", path), - code: 0, - } + return Err(RuntimeException::new(format!( + "Unable to determine svn url for path {}", + path + )) .into()); }; @@ -431,10 +417,11 @@ impl VcsDownloader for SvnDownloader { util.set_cache_credentials(self.cache_credentials.get()); util.execute_local(command.clone(), path, None, self.inner.io.is_verbose()) .map_err(|e| { - RuntimeException { - message: format!("Failed to execute {}\n\n{}", command.join(" "), e), - code: 0, - } + RuntimeException::new(format!( + "Failed to execute {}\n\n{}", + command.join(" "), + e + )) .into() }) } else { diff --git a/crates/shirabe/src/downloader/transport_exception.rs b/crates/shirabe/src/downloader/transport_exception.rs index 155a5ce4..da8b4c50 100644 --- a/crates/shirabe/src/downloader/transport_exception.rs +++ b/crates/shirabe/src/downloader/transport_exception.rs @@ -1,11 +1,10 @@ //! ref: composer/src/Composer/Downloader/TransportException.php -use shirabe_php_shim::PhpMixed; +use shirabe_php_shim::{PhpMixed, RuntimeException}; #[derive(Debug, Clone)] pub struct TransportException { - pub message: String, - pub code: i64, + inner: RuntimeException, pub(crate) headers: Option<Vec<String>>, pub(crate) response: Option<String>, pub(crate) status_code: Option<i64>, @@ -15,8 +14,7 @@ pub struct TransportException { impl TransportException { pub fn new(message: String, code: i64) -> Self { Self { - message, - code, + inner: RuntimeException::with_code(message, code), headers: None, response: None, status_code: None, @@ -24,20 +22,6 @@ impl TransportException { } } - /// PHP exposes ($message, $code = 0) — alias of `new` used at call sites where the - /// status/exception code is provided up-front. - pub fn new_with_code(message: String, code: i64) -> Self { - Self::new(message, code) - } - - pub fn get_code(&self) -> i64 { - self.code - } - - pub fn get_message(&self) -> &str { - &self.message - } - pub fn set_headers(&mut self, headers: Vec<String>) { self.headers = Some(headers); } @@ -71,10 +55,8 @@ impl TransportException { } } -impl std::fmt::Display for TransportException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.message) - } -} - -impl std::error::Error for TransportException {} +shirabe_php_shim::impl_php_exception!( + TransportException, + inner, + r"Composer\Downloader\TransportException" +); diff --git a/crates/shirabe/src/downloader/vcs_downloader.rs b/crates/shirabe/src/downloader/vcs_downloader.rs index 45ca0e3a..56bdea60 100644 --- a/crates/shirabe/src/downloader/vcs_downloader.rs +++ b/crates/shirabe/src/downloader/vcs_downloader.rs @@ -18,8 +18,9 @@ use crate::util::Filesystem; use crate::util::ProcessExecutor; use indexmap::IndexMap; use shirabe_php_shim::{ - InvalidArgumentException, PhpMixed, RuntimeException, array_map, array_shift, explode, - get_class_err, implode, rawurldecode, realpath, str_replace, strlen, strpos, substr, trim, + AnyThrowable, InvalidArgumentException, PhpClass as _, PhpMixed, RuntimeException, array_map, + array_shift, explode, implode, rawurldecode, realpath, str_replace, strlen, strpos, substr, + trim, }; #[derive(Debug)] @@ -129,13 +130,10 @@ pub trait VcsDownloader: prev_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<Option<PhpMixed>> { if package.get_source_reference().is_none() { - return Err(InvalidArgumentException { - message: format!( - "Package {} is missing reference information", - package.get_pretty_name(), - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package {} is missing reference information", + package.get_pretty_name(), + )) .into()); } @@ -157,7 +155,13 @@ pub trait VcsDownloader: } if self.io().is_debug() { self.io().write_error3( - &format!("Failed: [{}] {}", get_class_err(&e), e), + &format!( + "Failed: [{}] {}", + AnyThrowable::of(e.as_ref()) + .expect("PHP reaches this only with a caught \\Throwable") + .php_class_name(), + e + ), true, io_interface::NORMAL, ); @@ -232,13 +236,10 @@ pub trait VcsDownloader: path: &str, ) -> anyhow::Result<Option<PhpMixed>> { if package.get_source_reference().is_none() { - return Err(InvalidArgumentException { - message: format!( - "Package {} is missing reference information", - package.get_pretty_name(), - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package {} is missing reference information", + package.get_pretty_name(), + )) .into()); } @@ -264,7 +265,13 @@ pub trait VcsDownloader: } if self.io().is_debug() { self.io().write_error3( - &format!("Failed: [{}] {}", get_class_err(&e), e), + &format!( + "Failed: [{}] {}", + AnyThrowable::of(e.as_ref()) + .expect("PHP reaches this only with a caught \\Throwable") + .php_class_name(), + e + ), true, io_interface::NORMAL, ); @@ -292,13 +299,10 @@ pub trait VcsDownloader: path: &str, ) -> anyhow::Result<Option<PhpMixed>> { if target.get_source_reference().is_none() { - return Err(InvalidArgumentException { - message: format!( - "Package {} is missing reference information", - target.get_pretty_name(), - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package {} is missing reference information", + target.get_pretty_name(), + )) .into()); } @@ -333,7 +337,13 @@ pub trait VcsDownloader: } if self.io().is_debug() { self.io().write_error3( - &format!("Failed: [{}] {}", get_class_err(&e), e), + &format!( + "Failed: [{}] {}", + AnyThrowable::of(e.as_ref()) + .expect("PHP reaches this only with a caught \\Throwable") + .php_class_name(), + e + ), true, io_interface::NORMAL, ); @@ -400,10 +410,10 @@ pub trait VcsDownloader: let result = Filesystem::remove_directory_async_via(self.filesystem(), path).await?; if !result { - return Err(RuntimeException { - message: format!("Could not completely delete {}, aborting.", path), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not completely delete {}, aborting.", + path + )) .into()); } @@ -441,10 +451,10 @@ pub trait VcsDownloader: ) -> anyhow::Result<Option<PhpMixed>> { // the default implementation just fails if there are any changes, override in child classes to provide stash-ability if self.get_local_changes(package, path)?.is_some() { - return Err(RuntimeException { - message: format!("Source directory {} has uncommitted changes.", path), - code: 0, - } + return Err(RuntimeException::new(format!( + "Source directory {} has uncommitted changes.", + path + )) .into()); } diff --git a/crates/shirabe/src/downloader/zip_downloader.rs b/crates/shirabe/src/downloader/zip_downloader.rs index 8870194c..84ed4c9e 100644 --- a/crates/shirabe/src/downloader/zip_downloader.rs +++ b/crates/shirabe/src/downloader/zip_downloader.rs @@ -10,6 +10,7 @@ use crate::util::Platform; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; use shirabe_external_packages::symfony::process::ExecutableFinder; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ CmpOp, DIRECTORY_SEPARATOR, ErrorException, PhpMixed, RuntimeException, UnexpectedValueException, ZipArchive, bin2hex, class_exists, file_exists, file_get_contents, @@ -146,13 +147,10 @@ impl ZipDownloader { .borrow() .contains_key(&package.get_name()) { - return Err(RuntimeException { - message: format!( - "Failed to extract {} as the installation was aborted by another package operation.", - package.get_name() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to extract {} as the installation was aborted by another package operation.", + package.get_name() + )) .into()); } @@ -162,19 +160,16 @@ impl ZipDownloader { return self .try_fallback( - RuntimeException { - message: format!( - "Failed to extract {}: ({}) {}\n\n{}", - package.get_name(), - process - .get_exit_code() - .map(|c| c.to_string()) - .unwrap_or_default(), - command.join(" "), - output - ), - code: 0, - } + RuntimeException::new(format!( + "Failed to extract {}: ({}) {}\n\n{}", + package.get_name(), + process + .get_exit_code() + .map(|c| c.to_string()) + .unwrap_or_default(), + command.join(" "), + output + )) .into(), is_last_chance, file, @@ -337,13 +332,10 @@ impl ZipDownloader { && total_size > archive_sz * 100 && total_size > 50 * 1024 * 1024 { - return Err(RuntimeException { - message: format!( - "Invalid zip file for \"{}\" with compression ratio >99% (possible zip bomb)", - package.get_name(), - ), - code: 0, - }.into()); + return Err(RuntimeException::new(format!( + "Invalid zip file for \"{}\" with compression ratio >99% (possible zip bomb)", + package.get_name(), + )).into()); } } @@ -354,32 +346,26 @@ impl ZipDownloader { return Ok(None); } - Err(RuntimeException { - message: format!( - "There was an error extracting the ZIP file for \"{}\", it is either corrupted or using an invalid format.", - package.get_name(), - ), - code: 0, - }.into()) + Err(RuntimeException::new(format!( + "There was an error extracting the ZIP file for \"{}\", it is either corrupted or using an invalid format.", + package.get_name(), + )).into()) } - Err(code) => Err(UnexpectedValueException { - message: self.get_error_message(code, file).trim_end().to_string(), + Err(code) => Err(UnexpectedValueException::with_code( + self.get_error_message(code, file).trim_end().to_string(), code, - } + ) .into()), } })(); result.map_err(|e| { - if let Some(err) = e.downcast_ref::<ErrorException>() { - RuntimeException { - message: format!( - "The archive for \"{}\" may contain identical file names with different capitalization (which fails on case insensitive filesystems): {}", - package.get_name(), - err.message, - ), - code: 0, - }.into() + if let Some(err) = e.catch::<ErrorException>() { + RuntimeException::new(format!( + "The archive for \"{}\" may contain identical file names with different capitalization (which fails on case insensitive filesystems): {}", + package.get_name(), + err.get_message(), + )).into() } else { e } @@ -578,11 +564,7 @@ impl crate::downloader::DownloaderInterface for ZipDownloader { ini_message ) }; - return Err(RuntimeException { - message: error, - code: 0, - } - .into()); + return Err(RuntimeException::new(error).into()); } { |
