diff options
| -rw-r--r-- | crates/shirabe-external-packages/src/symfony/filesystem/exception/io_exception.rs | 2 | ||||
| -rw-r--r-- | crates/shirabe-php-shim/src/exception.rs | 70 | ||||
| -rw-r--r-- | crates/shirabe/src/command/exec_command.rs | 9 | ||||
| -rw-r--r-- | crates/shirabe/src/command/global_command.rs | 8 | ||||
| -rw-r--r-- | crates/shirabe/src/command/require_command.rs | 12 | ||||
| -rw-r--r-- | crates/shirabe/src/config/json_config_source.rs | 9 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/zip_downloader.rs | 6 | ||||
| -rw-r--r-- | crates/shirabe/src/json/json_file.rs | 8 | ||||
| -rw-r--r-- | crates/shirabe/src/package/archiver/phar_archiver.rs | 6 | ||||
| -rw-r--r-- | crates/shirabe/src/package/loader/array_loader.rs | 27 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/artifact_repository.rs | 14 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/composer_repository.rs | 18 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/path_repository.rs | 9 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/github_driver.rs | 18 | ||||
| -rw-r--r-- | crates/shirabe/src/util/git.rs | 17 |
15 files changed, 170 insertions, 63 deletions
diff --git a/crates/shirabe-external-packages/src/symfony/filesystem/exception/io_exception.rs b/crates/shirabe-external-packages/src/symfony/filesystem/exception/io_exception.rs index ffe80ea1..07a189c3 100644 --- a/crates/shirabe-external-packages/src/symfony/filesystem/exception/io_exception.rs +++ b/crates/shirabe-external-packages/src/symfony/filesystem/exception/io_exception.rs @@ -12,7 +12,7 @@ impl IOException { pub fn new( message: String, code: i64, - previous: Option<std::sync::Arc<shirabe_php_shim::AnyThrowable>>, + previous: Option<std::sync::Arc<anyhow::Error>>, path: Option<String>, ) -> Self { Self { diff --git a/crates/shirabe-php-shim/src/exception.rs b/crates/shirabe-php-shim/src/exception.rs index 803d5b64..fd5e4a53 100644 --- a/crates/shirabe-php-shim/src/exception.rs +++ b/crates/shirabe-php-shim/src/exception.rs @@ -1,12 +1,16 @@ use crate::PhpClass; -/// The fields a PHP `\Throwable` carries: its message and code, and the exception it wraps. Ported +/// The fields a PHP `\Throwable` carries: its message and code, and the error it wraps. Ported /// exception types embed this, either directly or through the parent exception they extend. +/// +/// `previous` is any error rather than an [`AnyThrowable`], because the port reaches a `catch` +/// carrying errors PHP would have raised as exception objects and the port raises as itself. One +/// that does carry an exception is still reachable as such, through [`Catch`]. #[derive(Debug, Clone)] pub struct ThrowableFields { message: String, code: i64, - previous: Option<std::sync::Arc<AnyThrowable>>, + previous: Option<std::sync::Arc<anyhow::Error>>, } impl ThrowableFields { @@ -23,7 +27,7 @@ impl ThrowableFields { self.code = code; } - pub fn get_previous(&self) -> Option<&AnyThrowable> { + pub fn get_previous(&self) -> Option<&anyhow::Error> { self.previous.as_deref() } } @@ -97,6 +101,15 @@ impl AnyThrowable { error.downcast_ref::<Self>() } + /// PHP has no `setPrevious`: an exception gets its `previous` from its constructor. The one + /// exception is a `finally` throwing over an exception already on its way out — the one the + /// `finally` threw propagates, and the engine makes the one it displaced its `previous`. + pub fn set_previous(&mut self, previous: std::sync::Arc<anyhow::Error>) { + self.downcast_mut::<ThrowableFields>() + .expect("every exception bottoms out at the ThrowableFields") + .previous = Some(previous); + } + /// PHP's `catch (T $e)`: the exception seen as an instance of `T`, or `None` if it is not one. /// A subclass answers through the instance of `T` it embeds, so `T`'s own state is reachable /// the way PHP reaches an inherited property. @@ -135,7 +148,7 @@ impl AnyThrowable { self.0.fields().get_code() } - pub fn get_previous(&self) -> Option<&AnyThrowable> { + pub fn get_previous(&self) -> Option<&anyhow::Error> { self.0.fields().get_previous() } } @@ -149,7 +162,7 @@ impl std::fmt::Display for AnyThrowable { impl std::error::Error for AnyThrowable { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.get_previous() - .map(|previous| previous as &(dyn std::error::Error + 'static)) + .map(|previous| &**previous as &(dyn std::error::Error + 'static)) } } @@ -277,7 +290,7 @@ macro_rules! impl_php_exception { self.$field.set_code(code); } - pub fn get_previous(&self) -> Option<&$crate::AnyThrowable> { + pub fn get_previous(&self) -> Option<&::anyhow::Error> { self.$field.get_previous() } } @@ -306,7 +319,7 @@ macro_rules! define_php_exception { pub fn with_code_and_previous( message: String, code: i64, - previous: Option<std::sync::Arc<AnyThrowable>>, + previous: Option<std::sync::Arc<anyhow::Error>>, ) -> Self { Self { inner: ThrowableFields { @@ -325,7 +338,7 @@ macro_rules! define_php_exception { pub fn with_code_and_previous( message: String, code: i64, - previous: Option<std::sync::Arc<AnyThrowable>>, + previous: Option<std::sync::Arc<anyhow::Error>>, ) -> Self { Self { inner: <$parent>::with_code_and_previous(message, code, previous), @@ -399,7 +412,7 @@ impl ErrorException { severity: i64, filename: String, lineno: i64, - previous: Option<std::sync::Arc<AnyThrowable>>, + previous: Option<std::sync::Arc<anyhow::Error>>, ) -> Self { Self { inner: Exception::with_code_and_previous(message, code, previous), @@ -518,8 +531,45 @@ mod tests { } #[test] + fn the_previous_is_any_error_a_catch_can_reach() { + let previous = std::sync::Arc::new(anyhow::Error::new(std::io::Error::other("io"))); + let error: anyhow::Error = + Exception::with_code_and_previous("boom".to_string(), 0, Some(previous)).into(); + + assert_eq!( + error + .catch::<Exception>() + .and_then(|e| e.get_previous()) + .map(ToString::to_string), + Some("io".to_string()) + ); + } + + #[test] + fn set_previous_reaches_the_superclass_holding_it() { + let pending = std::sync::Arc::new(anyhow::Error::from(RuntimeException::new( + "first".to_string(), + ))); + let mut error: anyhow::Error = Subclass::new(7).into(); + + error + .downcast_mut::<AnyThrowable>() + .unwrap() + .set_previous(pending); + + assert_eq!( + error + .catch::<Subclass>() + .and_then(|e| e.get_previous()) + .and_then(|previous| previous.catch::<RuntimeException>()) + .map(|previous| previous.get_message().to_string()), + Some("first".to_string()) + ); + } + + #[test] fn the_previous_exception_is_the_error_source() { - let previous = std::sync::Arc::new(AnyThrowable::new(RuntimeException::new( + let previous = std::sync::Arc::new(anyhow::Error::from(RuntimeException::new( "cause".to_string(), ))); let error: anyhow::Error = diff --git a/crates/shirabe/src/command/exec_command.rs b/crates/shirabe/src/command/exec_command.rs index a67c234e..61391117 100644 --- a/crates/shirabe/src/command/exec_command.rs +++ b/crates/shirabe/src/command/exec_command.rs @@ -220,10 +220,11 @@ impl Command for ExecCommand { && getcwd().as_deref() != Some(iwd.as_str()) { chdir(iwd).map_err(|e| { - RuntimeException::new(format!( - "Could not switch back to working directory \"{}\"", - iwd - )) + RuntimeException::with_code_and_previous( + format!("Could not switch back to working directory \"{}\"", iwd), + 0, + Some(std::sync::Arc::new(e)), + ) })?; } diff --git a/crates/shirabe/src/command/global_command.rs b/crates/shirabe/src/command/global_command.rs index 78b6f3e6..df9798a3 100644 --- a/crates/shirabe/src/command/global_command.rs +++ b/crates/shirabe/src/command/global_command.rs @@ -90,8 +90,12 @@ impl GlobalCommand { } } - chdir(&home).map_err(|_e| { - RuntimeException::new(format!("Could not switch to home directory \"{}\"", home)) + chdir(&home).map_err(|e| { + RuntimeException::with_code_and_previous( + format!("Could not switch to home directory \"{}\"", home), + 0, + Some(std::sync::Arc::new(e)), + ) })?; if !quiet { diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs index 84b4a6e4..88916175 100644 --- a/crates/shirabe/src/command/require_command.rs +++ b/crates/shirabe/src/command/require_command.rs @@ -977,10 +977,14 @@ impl Command for RequireCommand { if self.newly_created.get() { self.revert_composer_file(); - return Err(RuntimeException::new(format!( - "No composer.json present in the current directory ({}), this may be the cause of the following exception.", - self.file.borrow() - )) + return Err(RuntimeException::with_code_and_previous( + format!( + "No composer.json present in the current directory ({}), this may be the cause of the following exception.", + self.file.borrow() + ), + 0, + Some(std::sync::Arc::new(e)), + ) .into()); } diff --git a/crates/shirabe/src/config/json_config_source.rs b/crates/shirabe/src/config/json_config_source.rs index 0257ee31..59146630 100644 --- a/crates/shirabe/src/config/json_config_source.rs +++ b/crates/shirabe/src/config/json_config_source.rs @@ -148,11 +148,16 @@ impl JsonConfigSource { }; // restore contents to the original state file_put_contents(self.file.borrow().get_path(), contents.as_bytes()); - return Err(RuntimeException::new(format!( + let message = format!( "Failed to update composer.json with a valid format, reverting to the original content. Please report an issue to us with details (command you run and a copy of your composer.json). {}{}", PHP_EOL, implode(PHP_EOL, jve.get_errors()), - )) + ); + return Err(RuntimeException::with_code_and_previous( + message, + 0, + Some(std::sync::Arc::new(e)), + ) .into()); } } diff --git a/crates/shirabe/src/downloader/zip_downloader.rs b/crates/shirabe/src/downloader/zip_downloader.rs index 84ed4c9e..c481f13f 100644 --- a/crates/shirabe/src/downloader/zip_downloader.rs +++ b/crates/shirabe/src/downloader/zip_downloader.rs @@ -361,11 +361,13 @@ impl ZipDownloader { result.map_err(|e| { if let Some(err) = e.catch::<ErrorException>() { - RuntimeException::new(format!( + let message = 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() + ); + RuntimeException::with_code_and_previous(message, 0, Some(std::sync::Arc::new(e))) + .into() } else { e } diff --git a/crates/shirabe/src/json/json_file.rs b/crates/shirabe/src/json/json_file.rs index 2d061ced..da6370e4 100644 --- a/crates/shirabe/src/json/json_file.rs +++ b/crates/shirabe/src/json/json_file.rs @@ -174,7 +174,13 @@ impl JsonFile { // TransportException keeps its message verbatim; any other exception is wrapped // with the "Could not read" prefix. if let Some(te) = e.catch::<TransportException>() { - return Err(RuntimeException::new(te.get_message().to_string()).into()); + let message = te.get_message().to_string(); + return Err(RuntimeException::with_code_and_previous( + message, + 0, + Some(std::sync::Arc::new(e)), + ) + .into()); } return Err(RuntimeException::new(format!( "Could not read {}\n\n{}", diff --git a/crates/shirabe/src/package/archiver/phar_archiver.rs b/crates/shirabe/src/package/archiver/phar_archiver.rs index 0968f2a4..4a41d5a4 100644 --- a/crates/shirabe/src/package/archiver/phar_archiver.rs +++ b/crates/shirabe/src/package/archiver/phar_archiver.rs @@ -5,7 +5,7 @@ use crate::package::archiver::ArchivableFilesFinder; use crate::package::archiver::ArchiverInterface; use indexmap::IndexMap; use shirabe_php_shim::{ - FilesystemIterator, Phar, PharData, RuntimeException, bzcompress, file_exists, + AnyThrowable, FilesystemIterator, Phar, PharData, RuntimeException, bzcompress, file_exists, file_put_contents, function_exists, gzcompress, str_repeat, strrpos, unlink, }; @@ -147,7 +147,9 @@ impl ArchiverInterface for PharArchiver { "Could not create archive '{}' from '{}': {}", target_outer, sources, e ); - RuntimeException::new(message).into() + let code = AnyThrowable::of(e.as_ref()).map_or(0, AnyThrowable::get_code); + RuntimeException::with_code_and_previous(message, code, Some(std::sync::Arc::new(e))) + .into() }) } diff --git a/crates/shirabe/src/package/loader/array_loader.rs b/crates/shirabe/src/package/loader/array_loader.rs index 2c6d3971..106f094e 100644 --- a/crates/shirabe/src/package/loader/array_loader.rs +++ b/crates/shirabe/src/package/loader/array_loader.rs @@ -19,8 +19,9 @@ use chrono::Utc; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; use shirabe_php_shim::{ - E_USER_DEPRECATED, PhpMixed, UnexpectedValueException, is_scalar, is_string, json_encode, - ltrim, php_regex, stripos, strpos, strtolower, strval, substr, trigger_error, trim, + AnyThrowable, E_USER_DEPRECATED, PhpMixed, UnexpectedValueException, is_scalar, is_string, + json_encode, ltrim, php_regex, stripos, strpos, strtolower, strval, substr, trigger_error, + trim, }; #[derive(Debug)] @@ -112,11 +113,17 @@ impl ArrayLoader { { Ok(v) => version = v, Err(e) => { - return Err(UnexpectedValueException::new(format!( + let message = format!( "Failed to normalize version for package \"{}\": {}", config.get("name").and_then(|v| v.as_string()).unwrap_or(""), e - )) + ); + let code = AnyThrowable::of(e.as_ref()).map_or(0, AnyThrowable::get_code); + return Err(UnexpectedValueException::with_code_and_previous( + message, + code, + Some(std::sync::Arc::new(e)), + ) .into()); } } @@ -654,11 +661,17 @@ impl ArrayLoader { let parsed_constraint = match self.version_parser.parse_constraints(&constraint) { Ok(c) => c, - Err(_e) => { - return Err(UnexpectedValueException::new(format!( + Err(e) => { + let message = format!( "Link constraint in {} {} > {} should be a valid version constraint, got \"{}\"", source, description, target, constraint - )) + ); + let code = AnyThrowable::of(e.as_ref()).map_or(0, AnyThrowable::get_code); + return Err(UnexpectedValueException::with_code_and_previous( + message, + code, + Some(std::sync::Arc::new(e)), + ) .into()); } }; diff --git a/crates/shirabe/src/repository/artifact_repository.rs b/crates/shirabe/src/repository/artifact_repository.rs index 2d16f137..75ad134b 100644 --- a/crates/shirabe/src/repository/artifact_repository.rs +++ b/crates/shirabe/src/repository/artifact_repository.rs @@ -224,11 +224,15 @@ impl ArtifactRepository { .unwrap_or_default(); match self.loader.load(cfg, None) { Ok(package) => Ok(Some(package)), - Err(exception) => Err(UnexpectedValueException::new(format!( - "Failed loading package in {}: {}", - pathname, exception - )) - .into()), + Err(exception) => { + let message = format!("Failed loading package in {}: {}", pathname, exception); + Err(UnexpectedValueException::with_code_and_previous( + message, + 0, + Some(std::sync::Arc::new(exception)), + ) + .into()) + } } } } diff --git a/crates/shirabe/src/repository/composer_repository.rs b/crates/shirabe/src/repository/composer_repository.rs index 5371c2c3..b53374aa 100644 --- a/crates/shirabe/src/repository/composer_repository.rs +++ b/crates/shirabe/src/repository/composer_repository.rs @@ -40,9 +40,9 @@ use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; use shirabe_metadata_minifier::MetadataMinifier; use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ - CmpOp, InvalidArgumentException, LogicException, PHP_EOL, PhpMixed, RuntimeException, - UnexpectedValueException, extension_loaded, hash, http_build_query, json_decode, parse_url_all, - php_regex, realpath, strtolower, strtr, urlencode, var_export, + AnyThrowable, CmpOp, InvalidArgumentException, LogicException, PHP_EOL, PhpMixed, + RuntimeException, UnexpectedValueException, extension_loaded, hash, http_build_query, + json_decode, parse_url_all, php_regex, realpath, strtolower, strtr, urlencode, var_export, }; use shirabe_semver::CompilingMatcher; use shirabe_semver::constraint::AnyConstraint; @@ -2695,17 +2695,21 @@ impl ComposerRepository { })(); result.map_err(|e| { - RuntimeException::new(format!( + let message = format!( "Could not load packages in {}{}: [{}] {}", self.get_repo_name(), source .as_ref() .map(|s| format!(" from {}", s)) .unwrap_or_default(), - "Exception", + AnyThrowable::of(e.as_ref()).map_or_else( + || "Exception".to_string(), + shirabe_php_shim::PhpClass::php_class_name, + ), e - )) - .into() + ); + RuntimeException::with_code_and_previous(message, 0, Some(std::sync::Arc::new(e))) + .into() }) } diff --git a/crates/shirabe/src/repository/path_repository.rs b/crates/shirabe/src/repository/path_repository.rs index 313fc75e..46b7f95f 100644 --- a/crates/shirabe/src/repository/path_repository.rs +++ b/crates/shirabe/src/repository/path_repository.rs @@ -350,10 +350,11 @@ impl PathRepository { self.inner .add_package(self.loader.load(package.clone(), None).map_err(|e| { - RuntimeException::new(format!( - "Failed loading the package in {}", - composer_file_path - )) + RuntimeException::with_code_and_previous( + format!("Failed loading the package in {}", composer_file_path), + 0, + Some(std::sync::Arc::new(e)), + ) })?); } diff --git a/crates/shirabe/src/repository/vcs/github_driver.rs b/crates/shirabe/src/repository/vcs/github_driver.rs index 72bcc8ae..ffdf04ec 100644 --- a/crates/shirabe/src/repository/vcs/github_driver.rs +++ b/crates/shirabe/src/repository/vcs/github_driver.rs @@ -1038,7 +1038,7 @@ impl GitHubDriver { } if !self.inner.io.is_interactive() { - self.attempt_clone_fallback(Some(&e)) + self.attempt_clone_fallback(Some(std::sync::Arc::new((*e).into()))) .map_err(|err| TransportException::new(err.to_string(), 0))?; return Ok(Response::new( @@ -1090,7 +1090,7 @@ impl GitHubDriver { } if !self.inner.io.is_interactive() && fetching_repo_data { - self.attempt_clone_fallback(Some(&e)) + self.attempt_clone_fallback(Some(std::sync::Arc::new((*e).into()))) .map_err(|err| TransportException::new(err.to_string(), 0))?; return Ok(Response::new( @@ -1177,7 +1177,7 @@ impl GitHubDriver { } Err(e) => { if e.get_code() == 499 { - self.attempt_clone_fallback(Some(&e))?; + self.attempt_clone_fallback(Some(std::sync::Arc::new((*e).into())))?; } else { return Err((*e).into()); } @@ -1227,14 +1227,14 @@ impl GitHubDriver { /// @throws \RuntimeException pub(crate) fn attempt_clone_fallback( &mut self, - e: Option<&TransportException>, + e: Option<std::sync::Arc<anyhow::Error>>, ) -> anyhow::Result<bool> { if !self.allow_git_fallback { - return Err(RuntimeException::new(format!( - "Fallback to git driver disabled{}", - e.map(|e| format!(": {}", e.get_message())) - .unwrap_or_default() - )) + return Err(RuntimeException::with_code_and_previous( + "Fallback to git driver disabled".to_string(), + 0, + e, + ) .into()); } diff --git a/crates/shirabe/src/util/git.rs b/crates/shirabe/src/util/git.rs index 3eec0947..d7bd3685 100644 --- a/crates/shirabe/src/util/git.rs +++ b/crates/shirabe/src/util/git.rs @@ -16,7 +16,7 @@ use crate::util::{AuthHelper, StoreAuth}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; use shirabe_php_shim::{ - CmpOp, InvalidArgumentException, PHP_EOL, PhpMixed, RuntimeException, array_map, + AnyThrowable, CmpOp, InvalidArgumentException, PHP_EOL, PhpMixed, RuntimeException, array_map, clearstatcache, explode, implode, in_array_loose, in_array_strict, is_dir, php_regex, preg_quote, rawurldecode, rawurlencode, str_contains, str_ends_with, str_replace_array, strlen, strpos, substr, trim, version_compare, @@ -839,7 +839,7 @@ impl Git { Ok(()) })(); // finally - let _ = self.run_commands( + let finally_result = self.run_commands( vec