diff options
215 files changed, 3310 insertions, 4237 deletions
diff --git a/crates/shirabe-class-map-generator/src/class_map.rs b/crates/shirabe-class-map-generator/src/class_map.rs index 0b828be4..65eb3790 100644 --- a/crates/shirabe-class-map-generator/src/class_map.rs +++ b/crates/shirabe-class-map-generator/src/class_map.rs @@ -92,10 +92,11 @@ impl ClassMap { pub fn get_class_path(&self, class_name: &str) -> anyhow::Result<&str> { match self.map.get(class_name) { Some(path) => Ok(path.as_str()), - None => Err(anyhow::anyhow!(OutOfBoundsException { - message: format!("Class {} is not present in the map", class_name), - code: 0, - })), + None => Err(OutOfBoundsException::new(format!( + "Class {} is not present in the map", + class_name + )) + .into()), } } diff --git a/crates/shirabe-class-map-generator/src/class_map_generator.rs b/crates/shirabe-class-map-generator/src/class_map_generator.rs index 0f98ca27..d1a11c99 100644 --- a/crates/shirabe-class-map-generator/src/class_map_generator.rs +++ b/crates/shirabe-class-map-generator/src/class_map_generator.rs @@ -74,19 +74,15 @@ impl ClassMapGenerator { excluded_dirs: Vec<String>, ) -> anyhow::Result<()> { if !matches!(autoload_type, "psr-0" | "psr-4" | "classmap") { - return Err(anyhow::anyhow!(InvalidArgumentException { - message: "$autoloadType must be one of: \"psr-0\", \"psr-4\" or \"classmap\"" - .to_string(), - code: 0, - })); + return Err(InvalidArgumentException::new( + "$autoloadType must be one of: \"psr-0\", \"psr-4\" or \"classmap\"".to_string(), + ) + .into()); } let base_path: Option<String> = if autoload_type != "classmap" { if namespace.is_none() { - return Err(anyhow::anyhow!(InvalidArgumentException { - message: "$namespace must be given (even if it is an empty string if you do not want to filter) when specifying a psr-0 or psr-4 autoload type".to_string(), - code: 0, - })); + return Err(InvalidArgumentException::new("$namespace must be given (even if it is an empty string if you do not want to filter) when specifying a psr-0 or psr-4 autoload type".to_string()).into()); } Some(path.to_owned()) } else { @@ -116,13 +112,10 @@ impl ClassMapGenerator { .iter() .collect() } else { - return Err(anyhow::anyhow!(RuntimeException { - message: format!( - "Could not scan for classes inside \"{}\" which does not appear to be a file nor a folder", - path - ), - code: 0, - })); + return Err(RuntimeException::new(format!( + "Could not scan for classes inside \"{}\" which does not appear to be a file nor a folder", + path + )).into()); }; let cwd = realpath(getcwd().unwrap_or_default()).unwrap_or_default(); @@ -131,10 +124,11 @@ impl ClassMapGenerator { let mut file_path = match file.to_str() { Some(s) => s.to_string(), None => { - return Err(anyhow::anyhow!(RuntimeException { - message: format!("Path contains invalid UTF-8: {}", file.display()), - code: 0, - })); + return Err(RuntimeException::new(format!( + "Path contains invalid UTF-8: {}", + file.display() + )) + .into()); } }; let ext = pathinfo(&file_path, PATHINFO_EXTENSION); @@ -158,10 +152,11 @@ impl ClassMapGenerator { } if file_path.is_empty() { - return Err(anyhow::anyhow!(LogicException { - message: format!("Got an empty $filePath for {}", file.display()), - code: 0, - })); + return Err(LogicException::new(format!( + "Got an empty $filePath for {}", + file.display() + )) + .into()); } let real_path = if is_stream_wrapper_path { @@ -170,13 +165,11 @@ impl ClassMapGenerator { match realpath(&file_path) { Some(p) => p, None => { - return Err(anyhow::anyhow!(RuntimeException { - message: format!( - "realpath of {} failed to resolve, got false", - file_path - ), - code: 0, - })); + return Err(RuntimeException::new(format!( + "realpath of {} failed to resolve, got false", + file_path + )) + .into()); } } }; @@ -284,10 +277,10 @@ impl ClassMapGenerator { }; sub_path = str_replace("\\", DIRECTORY_SEPARATOR, &sub_namespace); } else { - return Err(anyhow::anyhow!(InvalidArgumentException { - message: "$namespaceType must be \"psr-0\" or \"psr-4\"".to_string(), - code: 0, - })); + return Err(InvalidArgumentException::new( + "$namespaceType must be \"psr-0\" or \"psr-4\"".to_string(), + ) + .into()); } if sub_path == real_sub_path { @@ -403,10 +396,10 @@ impl ClassMapGenerator { fn get_cwd() -> anyhow::Result<String> { match getcwd() { Some(cwd) => Ok(cwd), - None => Err(anyhow::anyhow!(RuntimeException { - message: "Could not determine the current working directory".to_string(), - code: 0, - })), + None => Err(RuntimeException::new( + "Could not determine the current working directory".to_string(), + ) + .into()), } } } diff --git a/crates/shirabe-class-map-generator/src/php_file_parser.rs b/crates/shirabe-class-map-generator/src/php_file_parser.rs index cf30c240..88676407 100644 --- a/crates/shirabe-class-map-generator/src/php_file_parser.rs +++ b/crates/shirabe-class-map-generator/src/php_file_parser.rs @@ -1,7 +1,6 @@ //! ref: composer/vendor/composer/class-map-generator/src/PhpFileParser.php use crate::php_file_cleaner::PhpFileCleaner; -use anyhow::anyhow; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; use shirabe_php_shim::{ @@ -18,10 +17,7 @@ impl PhpFileParser { let extra_types = Self::get_extra_types(); if !function_exists("php_strip_whitespace") { - return Err(anyhow!(RuntimeException { - message: "Classmap generation relies on the php_strip_whitespace function, but it has been disabled by the disable_functions directive.".to_string(), - code: 0, - })); + return Err(RuntimeException::new("Classmap generation relies on the php_strip_whitespace function, but it has been disabled by the disable_functions directive.".to_string()).into()); } // Use @ here instead of Silencer to actively suppress 'unhelpful' output @@ -63,7 +59,7 @@ impl PhpFileParser { ); } - return Err(anyhow!(RuntimeException { message, code: 0 })); + return Err(RuntimeException::new(message).into()); } // return early if there is no chance of matching anything in this file diff --git a/crates/shirabe-external-packages/src/seld/json_lint/parsing_exception.rs b/crates/shirabe-external-packages/src/seld/json_lint/parsing_exception.rs index 867f18e3..9d3eaa57 100644 --- a/crates/shirabe-external-packages/src/seld/json_lint/parsing_exception.rs +++ b/crates/shirabe-external-packages/src/seld/json_lint/parsing_exception.rs @@ -25,33 +25,21 @@ pub struct ParsingExceptionDetails { #[derive(Debug)] pub struct ParsingException { - pub message: String, - pub code: i64, + inner: shirabe_php_shim::Exception, pub(crate) details: Box<ParsingExceptionDetails>, } impl ParsingException { pub fn new(message: String, details: ParsingExceptionDetails) -> Self { Self { - message, - code: 0, + inner: shirabe_php_shim::Exception::new(message), details: Box::new(details), } } - pub fn get_message(&self) -> &str { - &self.message - } - pub fn get_details(&self) -> &ParsingExceptionDetails { &self.details } } -impl std::fmt::Display for ParsingException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.message) - } -} - -impl std::error::Error for ParsingException {} +shirabe_php_shim::impl_php_exception!(ParsingException, inner, r"Seld\JsonLint\ParsingException"); diff --git a/crates/shirabe-external-packages/src/symfony/console/color.rs b/crates/shirabe-external-packages/src/symfony/console/color.rs index 79d0c58b..f1aa4332 100644 --- a/crates/shirabe-external-packages/src/symfony/console/color.rs +++ b/crates/shirabe-external-packages/src/symfony/console/color.rs @@ -75,22 +75,17 @@ impl Color { for option in options { let available = available_options_get(option); if available.is_none() { - return Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!( - "Invalid option specified: \"{}\". Expected one of ({}).", - option.clone(), - shirabe_php_shim::implode( - ", ", - &AVAILABLE_OPTIONS - .iter() - .map(|(k, _)| k.to_string()) - .collect::<Vec<String>>(), - ), - ), - code: 0, - }, - )); + return Err(InvalidArgumentException::new(format!( + "Invalid option specified: \"{}\". Expected one of ({}).", + option.clone(), + shirabe_php_shim::implode( + ", ", + &AVAILABLE_OPTIONS + .iter() + .map(|(k, _)| k.to_string()) + .collect::<Vec<String>>(), + ), + ))); } this.options.insert(option.clone(), available.unwrap()); @@ -153,12 +148,10 @@ impl Color { } if shirabe_php_shim::strlen(&color) != 6 { - return Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!("Invalid \"{}\" color.", color), - code: 0, - }, - )); + return Err(InvalidArgumentException::new(format!( + "Invalid \"{}\" color.", + color + ))); } return Ok(format!( @@ -178,16 +171,11 @@ impl Color { let mut available: Vec<String> = COLORS.iter().map(|(k, _)| k.to_string()).collect(); available.extend(BRIGHT_COLORS.iter().map(|(k, _)| k.to_string())); - Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!( - "Invalid \"{}\" color; expected one of ({}).", - color, - shirabe_php_shim::implode(", ", &available), - ), - code: 0, - }, - )) + Err(InvalidArgumentException::new(format!( + "Invalid \"{}\" color; expected one of ({}).", + color, + shirabe_php_shim::implode(", ", &available), + ))) } fn convert_hex_color_to_ansi(color: i64) -> String { diff --git a/crates/shirabe-external-packages/src/symfony/console/command/command.rs b/crates/shirabe-external-packages/src/symfony/console/command/command.rs index ca61717d..a1636848 100644 --- a/crates/shirabe-external-packages/src/symfony/console/command/command.rs +++ b/crates/shirabe-external-packages/src/symfony/console/command/command.rs @@ -138,12 +138,10 @@ impl CommandData { let mut matches: Vec<Option<String>> = Vec::new(); if !shirabe_php_shim::preg_match(php_regex!(r"/^[^\:]++(\:[^\:]++)*$/"), name, &mut matches) { - return Ok(Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!("Command name \"{}\" is invalid.", name), - code: 0, - }, - ))); + return Ok(Err(InvalidArgumentException::new(format!( + "Command name \"{}\" is invalid.", + name + )))); } Ok(Ok(())) @@ -847,14 +845,11 @@ impl Command for CommandData { let helper_set = match &*helper_set_ref { None => { return Ok(Err( - crate::symfony::console::exception::logic_exception::LogicException( - shirabe_php_shim::LogicException { - message: format!( - "Cannot retrieve helper \"{}\" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.", - name - ), - code: 0, - }, + crate::symfony::console::exception::logic_exception::LogicException::new( + format!( + "Cannot retrieve helper \"{}\" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.", + name + ), ), )); } diff --git a/crates/shirabe-external-packages/src/symfony/console/command/complete_command.rs b/crates/shirabe-external-packages/src/symfony/console/command/complete_command.rs index cc8747ec..5854e248 100644 --- a/crates/shirabe-external-packages/src/symfony/console/command/complete_command.rs +++ b/crates/shirabe-external-packages/src/symfony/console/command/complete_command.rs @@ -81,11 +81,9 @@ impl CompleteCommand { ) -> anyhow::Result<CompletionInput> { let current_index = input.get_option("current")?; if !current_index.to_bool() || !shirabe_php_shim::ctype_digit(¤t_index.to_string()) { - anyhow::bail!(shirabe_php_shim::RuntimeException { - message: "The \"--current\" option must be set and it must be an integer." - .to_string(), - code: 0, - }); + anyhow::bail!(shirabe_php_shim::RuntimeException::new( + "The \"--current\" option must be set and it must be an integer.".to_string() + )); } let tokens: Vec<String> = match input.get_option("input")?.as_list() { @@ -251,10 +249,9 @@ impl Command for CompleteCommand { let shell = input.borrow().get_option("shell")?; if !shell.to_bool() { - anyhow::bail!(shirabe_php_shim::RuntimeException { - message: "The \"--shell\" option must be set.".to_string(), - code: 0, - }); + anyhow::bail!(shirabe_php_shim::RuntimeException::new( + "The \"--shell\" option must be set.".to_string() + )); } let completion_output = self @@ -263,18 +260,15 @@ impl Command for CompleteCommand { .cloned() .unwrap_or(PhpMixed::Bool(false)); if !completion_output.to_bool() { - anyhow::bail!(shirabe_php_shim::RuntimeException { - message: format!( - "Shell completion is not supported for your shell: \"{}\" (supported: \"{}\").", - shell, - self.completion_outputs - .keys() - .cloned() - .collect::<Vec<_>>() - .join("\", \"") - ), - code: 0, - }); + anyhow::bail!(shirabe_php_shim::RuntimeException::new(format!( + "Shell completion is not supported for your shell: \"{}\" (supported: \"{}\").", + shell, + self.completion_outputs + .keys() + .cloned() + .collect::<Vec<_>>() + .join("\", \"") + ))); } let mut completion_input = self.create_completion_input(&*input.borrow())?; diff --git a/crates/shirabe-external-packages/src/symfony/console/exception/command_not_found_exception.rs b/crates/shirabe-external-packages/src/symfony/console/exception/command_not_found_exception.rs index 5a6959d4..4b27c5e6 100644 --- a/crates/shirabe-external-packages/src/symfony/console/exception/command_not_found_exception.rs +++ b/crates/shirabe-external-packages/src/symfony/console/exception/command_not_found_exception.rs @@ -1,21 +1,17 @@ //! ref: composer/vendor/symfony/console/Exception/CommandNotFoundException.php use super::exception_interface::ExceptionInterface; -use super::invalid_argument_exception::InvalidArgumentException; #[derive(Debug)] pub struct CommandNotFoundException { - inner: InvalidArgumentException, + inner: shirabe_php_shim::InvalidArgumentException, alternatives: Vec<String>, } impl CommandNotFoundException { pub fn new(message: String, alternatives: Vec<String>, code: i64) -> Self { Self { - inner: InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message, - code, - }), + inner: shirabe_php_shim::InvalidArgumentException::with_code(message, code), alternatives, } } @@ -25,12 +21,10 @@ impl CommandNotFoundException { } } -impl std::fmt::Display for CommandNotFoundException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.inner) - } -} - -impl std::error::Error for CommandNotFoundException {} +shirabe_php_shim::impl_php_exception!( + CommandNotFoundException, + inner, + r"Symfony\Component\Console\Exception\CommandNotFoundException" +); impl ExceptionInterface for CommandNotFoundException {} diff --git a/crates/shirabe-external-packages/src/symfony/console/exception/exception_interface.rs b/crates/shirabe-external-packages/src/symfony/console/exception/exception_interface.rs index ce279fdb..bb2200f6 100644 --- a/crates/shirabe-external-packages/src/symfony/console/exception/exception_interface.rs +++ b/crates/shirabe-external-packages/src/symfony/console/exception/exception_interface.rs @@ -1,3 +1,3 @@ //! ref: composer/vendor/symfony/console/Exception/ExceptionInterface.php -pub trait ExceptionInterface: std::error::Error {} +pub trait ExceptionInterface: shirabe_php_shim::Throwable {} diff --git a/crates/shirabe-external-packages/src/symfony/console/exception/invalid_argument_exception.rs b/crates/shirabe-external-packages/src/symfony/console/exception/invalid_argument_exception.rs index ac3be3b2..ced79b44 100644 --- a/crates/shirabe-external-packages/src/symfony/console/exception/invalid_argument_exception.rs +++ b/crates/shirabe-external-packages/src/symfony/console/exception/invalid_argument_exception.rs @@ -5,12 +5,16 @@ use super::exception_interface::ExceptionInterface; #[derive(Debug)] pub struct InvalidArgumentException(pub shirabe_php_shim::InvalidArgumentException); -impl std::fmt::Display for InvalidArgumentException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) +impl InvalidArgumentException { + pub fn new(message: String) -> Self { + Self(shirabe_php_shim::InvalidArgumentException::new(message)) } } -impl std::error::Error for InvalidArgumentException {} +shirabe_php_shim::impl_php_exception!( + InvalidArgumentException, + 0, + r"Symfony\Component\Console\Exception\InvalidArgumentException" +); impl ExceptionInterface for InvalidArgumentException {} diff --git a/crates/shirabe-external-packages/src/symfony/console/exception/invalid_option_exception.rs b/crates/shirabe-external-packages/src/symfony/console/exception/invalid_option_exception.rs index 2cc23817..04de3d42 100644 --- a/crates/shirabe-external-packages/src/symfony/console/exception/invalid_option_exception.rs +++ b/crates/shirabe-external-packages/src/symfony/console/exception/invalid_option_exception.rs @@ -1,17 +1,21 @@ //! ref: composer/vendor/symfony/console/Exception/InvalidOptionException.php use super::exception_interface::ExceptionInterface; -use super::invalid_argument_exception::InvalidArgumentException; +use shirabe_php_shim::InvalidArgumentException; #[derive(Debug)] pub struct InvalidOptionException(pub InvalidArgumentException); -impl std::fmt::Display for InvalidOptionException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) +impl InvalidOptionException { + pub fn new(message: String) -> Self { + Self(InvalidArgumentException::new(message)) } } -impl std::error::Error for InvalidOptionException {} +shirabe_php_shim::impl_php_exception!( + InvalidOptionException, + 0, + r"Symfony\Component\Console\Exception\InvalidOptionException" +); impl ExceptionInterface for InvalidOptionException {} diff --git a/crates/shirabe-external-packages/src/symfony/console/exception/logic_exception.rs b/crates/shirabe-external-packages/src/symfony/console/exception/logic_exception.rs index 2c782195..03d9241f 100644 --- a/crates/shirabe-external-packages/src/symfony/console/exception/logic_exception.rs +++ b/crates/shirabe-external-packages/src/symfony/console/exception/logic_exception.rs @@ -5,12 +5,16 @@ use super::exception_interface::ExceptionInterface; #[derive(Debug)] pub struct LogicException(pub shirabe_php_shim::LogicException); -impl std::fmt::Display for LogicException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) +impl LogicException { + pub fn new(message: String) -> Self { + Self(shirabe_php_shim::LogicException::new(message)) } } -impl std::error::Error for LogicException {} +shirabe_php_shim::impl_php_exception!( + LogicException, + 0, + r"Symfony\Component\Console\Exception\LogicException" +); impl ExceptionInterface for LogicException {} diff --git a/crates/shirabe-external-packages/src/symfony/console/exception/missing_input_exception.rs b/crates/shirabe-external-packages/src/symfony/console/exception/missing_input_exception.rs index 65474c2b..f9ddc0e6 100644 --- a/crates/shirabe-external-packages/src/symfony/console/exception/missing_input_exception.rs +++ b/crates/shirabe-external-packages/src/symfony/console/exception/missing_input_exception.rs @@ -6,12 +6,16 @@ use super::runtime_exception::RuntimeException; #[derive(Debug)] pub struct MissingInputException(pub RuntimeException); -impl std::fmt::Display for MissingInputException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) +impl MissingInputException { + pub fn new(message: String) -> Self { + Self(RuntimeException::new(message)) } } -impl std::error::Error for MissingInputException {} +shirabe_php_shim::impl_php_exception!( + MissingInputException, + 0, + r"Symfony\Component\Console\Exception\MissingInputException" +); impl ExceptionInterface for MissingInputException {} diff --git a/crates/shirabe-external-packages/src/symfony/console/exception/namespace_not_found_exception.rs b/crates/shirabe-external-packages/src/symfony/console/exception/namespace_not_found_exception.rs index c593a3f7..31da0305 100644 --- a/crates/shirabe-external-packages/src/symfony/console/exception/namespace_not_found_exception.rs +++ b/crates/shirabe-external-packages/src/symfony/console/exception/namespace_not_found_exception.rs @@ -6,12 +6,16 @@ use super::exception_interface::ExceptionInterface; #[derive(Debug)] pub struct NamespaceNotFoundException(pub CommandNotFoundException); -impl std::fmt::Display for NamespaceNotFoundException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) +impl NamespaceNotFoundException { + pub fn new(message: String, alternatives: Vec<String>, code: i64) -> Self { + Self(CommandNotFoundException::new(message, alternatives, code)) } } -impl std::error::Error for NamespaceNotFoundException {} +shirabe_php_shim::impl_php_exception!( + NamespaceNotFoundException, + 0, + r"Symfony\Component\Console\Exception\NamespaceNotFoundException" +); impl ExceptionInterface for NamespaceNotFoundException {} diff --git a/crates/shirabe-external-packages/src/symfony/console/exception/runtime_exception.rs b/crates/shirabe-external-packages/src/symfony/console/exception/runtime_exception.rs index cfc64774..12e5b79c 100644 --- a/crates/shirabe-external-packages/src/symfony/console/exception/runtime_exception.rs +++ b/crates/shirabe-external-packages/src/symfony/console/exception/runtime_exception.rs @@ -5,12 +5,16 @@ use super::exception_interface::ExceptionInterface; #[derive(Debug)] pub struct RuntimeException(pub shirabe_php_shim::RuntimeException); -impl std::fmt::Display for RuntimeException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) +impl RuntimeException { + pub fn new(message: String) -> Self { + Self(shirabe_php_shim::RuntimeException::new(message)) } } -impl std::error::Error for RuntimeException {} +shirabe_php_shim::impl_php_exception!( + RuntimeException, + 0, + r"Symfony\Component\Console\Exception\RuntimeException" +); impl ExceptionInterface for RuntimeException {} diff --git a/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter.rs b/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter.rs index f8a5db4c..a0562aef 100644 --- a/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter.rs +++ b/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter.rs @@ -253,15 +253,11 @@ impl OutputFormatterInterface for OutputFormatter { ) -> anyhow::Result<std::rc::Rc<std::cell::RefCell<Box<dyn OutputFormatterStyleInterface>>>> { if !self.has_style(name) { - return Err(anyhow::anyhow!(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!( - "Undefined style: \"{}\".", - shirabe_php_shim::PhpMixed::String(name.to_string()), - ), - code: 0, - }, - ))); + return Err(InvalidArgumentException::new(format!( + "Undefined style: \"{}\".", + shirabe_php_shim::PhpMixed::String(name.to_string()), + )) + .into()); } Ok(std::rc::Rc::clone( diff --git a/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style_stack.rs b/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style_stack.rs index 2464c028..acfd8878 100644 --- a/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style_stack.rs +++ b/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style_stack.rs @@ -57,11 +57,8 @@ impl OutputFormatterStyleStack { } } - Ok(Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: "Incorrectly nested style tag found.".to_string(), - code: 0, - }, + Ok(Err(InvalidArgumentException::new( + "Incorrectly nested style tag found.".to_string(), ))) } diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/descriptor_helper.rs b/crates/shirabe-external-packages/src/symfony/console/helper/descriptor_helper.rs index 05f0d7d1..36603b06 100644 --- a/crates/shirabe-external-packages/src/symfony/console/helper/descriptor_helper.rs +++ b/crates/shirabe-external-packages/src/symfony/console/helper/descriptor_helper.rs @@ -79,13 +79,11 @@ impl DescriptorHelper { }; if !self.descriptors.contains_key(&format) { - return Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: format!("Unsupported format \"{}\".", format.clone()), - code: 0, - }) - .into(), - ); + return Err(InvalidArgumentException::new(format!( + "Unsupported format \"{}\".", + format.clone() + )) + .into()); } let descriptor = self.descriptors.get_mut(&format).unwrap(); diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/process_helper.rs b/crates/shirabe-external-packages/src/symfony/console/helper/process_helper.rs index 312d8e15..0f81348d 100644 --- a/crates/shirabe-external-packages/src/symfony/console/helper/process_helper.rs +++ b/crates/shirabe-external-packages/src/symfony/console/helper/process_helper.rs @@ -103,13 +103,10 @@ impl ProcessHelper { }; } None => { - anyhow::bail!(shirabe_php_shim::InvalidArgumentException { - message: format!( - "Invalid command provided to \"{}()\": the command should be an array whose first element is either the path to the binary to run or a \"Process\" object.", - shirabe_php_shim::PhpMixed::String("ProcessHelper::run".to_string()), - ), - code: 0, - }); + anyhow::bail!(shirabe_php_shim::InvalidArgumentException::new(format!( + "Invalid command provided to \"{}()\": the command should be an array whose first element is either the path to the binary to run or a \"Process\" object.", + shirabe_php_shim::PhpMixed::String("ProcessHelper::run".to_string()), + ))); } } diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/progress_bar.rs b/crates/shirabe-external-packages/src/symfony/console/helper/progress_bar.rs index 0abee1e7..7bfbfbcd 100644 --- a/crates/shirabe-external-packages/src/symfony/console/helper/progress_bar.rs +++ b/crates/shirabe-external-packages/src/symfony/console/helper/progress_bar.rs @@ -642,10 +642,7 @@ impl ProgressBar { "remaining".to_string(), Box::new(|bar: &ProgressBar, _output: &std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>| { if bar.get_max_steps() == 0 { - return Ok(Err(LogicException(shirabe_php_shim::LogicException { - message: "Unable to display the remaining time if the maximum number of steps is not set.".to_string(), - code: 0, - }))); + return Ok(Err(LogicException::new("Unable to display the remaining time if the maximum number of steps is not set.".to_string()))); } Ok(Ok(shirabe_php_shim::PhpMixed::String( @@ -658,10 +655,7 @@ impl ProgressBar { "estimated".to_string(), Box::new(|bar: &ProgressBar, _output: &std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>| { if bar.get_max_steps() == 0 { - return Ok(Err(LogicException(shirabe_php_shim::LogicException { - message: "Unable to display the estimated time if the maximum number of steps is not set.".to_string(), - code: 0, - }))); + return Ok(Err(LogicException::new("Unable to display the estimated time if the maximum number of steps is not set.".to_string()))); } Ok(Ok(shirabe_php_shim::PhpMixed::String( @@ -814,10 +808,7 @@ impl ProgressBar { let formatter = formatters.as_ref().unwrap().get(&name).unwrap(); formatter(self, &self.output) }); - match formatter_result? { - Ok(text) => text, - Err(e) => return Err(anyhow::Error::new(e)), - } + formatter_result?? } else if let Some(message) = self.messages.get(&name) { shirabe_php_shim::PhpMixed::String(message.clone()) } else { diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs b/crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs index 987c43f1..2091225d 100644 --- a/crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs +++ b/crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs @@ -185,12 +185,7 @@ pub trait QuestionHelperInterface { } if matches!(read, PhpMixed::Bool(false)) { - return Ok(Err(MissingInputException(RuntimeException( - shirabe_php_shim::RuntimeException { - message: "Aborted.".to_string(), - code: 0, - }, - )))); + return Ok(Err(MissingInputException::new("Aborted.".to_string()))); } r = read; if question.is_trimmable() { @@ -263,17 +258,19 @@ pub trait QuestionHelperInterface { // The validator return type is fixed to InvalidArgumentException here, so the // RuntimeException rethrow branch is statically unreachable; record the error // and retry. - error = Some(shirabe_php_shim::Exception { - message: e.0.message.clone(), - code: e.0.code, - }); + error = Some(shirabe_php_shim::Exception::with_code( + e.get_message().to_string(), + e.get_code(), + )); } } } // throw $error; Err(anyhow::Error::msg( - error.map(|e| e.message).unwrap_or_default(), + error + .map(|e| e.get_message().to_string()) + .unwrap_or_default(), )) } @@ -415,12 +412,12 @@ impl QuestionHelper { let formatter = helper_set.borrow().get_formatter(); formatter.borrow().format_block( - FormatBlockMessages::String(error.message.clone()), + FormatBlockMessages::String(error.get_message().to_string()), "error", false, ) } else { - format!("<error>{}</error>", error.message) + format!("<error>{}</error>", error.get_message()) }; output @@ -491,12 +488,7 @@ impl QuestionHelper { && matches!(question.get_default(), PhpMixed::Null)) { shirabe_php_shim::shell_exec(&format!("stty {}", stty_mode)); - return Err(MissingInputException(RuntimeException( - shirabe_php_shim::RuntimeException { - message: "Aborted.".to_string(), - code: 0, - }, - ))); + return Err(MissingInputException::new("Aborted.".to_string())); } else if c.as_deref() == Some("\u{7f}") { // Backspace Character if 0 == num_matches && 0 != i { @@ -730,10 +722,9 @@ impl QuestionHelper { stty_mode = shirabe_php_shim::shell_exec("stty -g").unwrap_or_default(); shirabe_php_shim::shell_exec("stty -echo"); } else if self.is_interactive_input(input_stream) { - return Ok(Err(RuntimeException(shirabe_php_shim::RuntimeException { - message: "Unable to hide the response.".to_string(), - code: 0, - }))); + return Ok(Err(RuntimeException::new( + "Unable to hide the response.".to_string(), + ))); } let value = shirabe_php_shim::fgets(input_stream, Some(4096)); @@ -745,13 +736,7 @@ impl QuestionHelper { let mut value = match value { Some(value) => value, None => { - return Err(MissingInputException(RuntimeException( - shirabe_php_shim::RuntimeException { - message: "Aborted.".to_string(), - code: 0, - }, - )) - .into()); + return Err(MissingInputException::new("Aborted.".to_string()).into()); } }; if trimmable { diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/symfony_question_helper.rs b/crates/shirabe-external-packages/src/symfony/console/helper/symfony_question_helper.rs index cae3434d..eda54243 100644 --- a/crates/shirabe-external-packages/src/symfony/console/helper/symfony_question_helper.rs +++ b/crates/shirabe-external-packages/src/symfony/console/helper/symfony_question_helper.rs @@ -138,7 +138,7 @@ impl QuestionHelperInterface for SymfonyQuestionHelper { let mut borrowed = output.borrow_mut(); if let Some(style) = (*borrowed).as_any_mut().downcast_mut::<SymfonyStyle>() { style.new_line(1); - style.error(PhpMixed::String(error.message.clone())); + style.error(PhpMixed::String(error.get_message().to_string())); return; } diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/table.rs b/crates/shirabe-external-packages/src/symfony/console/helper/table.rs index 66ab3174..a1f5a11e 100644 --- a/crates/shirabe-external-packages/src/symfony/console/helper/table.rs +++ b/crates/shirabe-external-packages/src/symfony/console/helper/table.rs @@ -325,12 +325,10 @@ impl Table { return Ok(Ok(style.clone())); } - Ok(Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!("Style \"{}\" is not defined.", name), - code: 0, - }, - ))) + Ok(Err(InvalidArgumentException::new(format!( + "Style \"{}\" is not defined.", + name + )))) } /// Sets table style. @@ -454,14 +452,11 @@ impl Table { /// Adds a row to the table, and re-renders the table. pub fn append_row(&mut self, row: Row) -> anyhow::Result<Result<&mut Self, RuntimeException>> { if !Self::output_is_console_section(&self.output) { - return Ok(Err(RuntimeException(shirabe_php_shim::RuntimeException { - message: format!( - "Output should be an instance of \"{}\" when calling \"{}\".", - "Symfony\\Component\\Console\\Output\\ConsoleSectionOutput", - "Symfony\\Component\\Console\\Helper\\Table::appendRow", - ), - code: 0, - }))); + return Ok(Err(RuntimeException::new(format!( + "Output should be an instance of \"{}\" when calling \"{}\".", + "Symfony\\Component\\Console\\Output\\ConsoleSectionOutput", + "Symfony\\Component\\Console\\Helper\\Table::appendRow", + )))); } if self.rendered { @@ -1373,12 +1368,10 @@ impl Table { return Ok(Ok(style.clone())); } - Ok(Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!("Style \"{}\" is not defined.", name), - code: 0, - }, - ))) + Ok(Err(InvalidArgumentException::new(format!( + "Style \"{}\" is not defined.", + name + )))) } fn formatter_is_wrappable( diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/table_cell.rs b/crates/shirabe-external-packages/src/symfony/console/helper/table_cell.rs index 2b1e592b..c899e5f7 100644 --- a/crates/shirabe-external-packages/src/symfony/console/helper/table_cell.rs +++ b/crates/shirabe-external-packages/src/symfony/console/helper/table_cell.rs @@ -35,27 +35,18 @@ impl TableCell { .cloned() .collect(); if !diff.is_empty() { - return Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!( - "The TableCell does not support the following options: '{}'.", - diff.join("', '"), - ), - code: 0, - }, - )); + return Err(InvalidArgumentException::new(format!( + "The TableCell does not support the following options: '{}'.", + diff.join("', '"), + ))); } if let Some(style) = options.get("style") && !matches!(style, TableCellOption::Style(_)) && !matches!(style, TableCellOption::Null) { - return Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: "The style option must be an instance of \"TableCellStyle\"." - .to_string(), - code: 0, - }, + return Err(InvalidArgumentException::new( + "The style option must be an instance of \"TableCellStyle\".".to_string(), )); } diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/table_cell_style.rs b/crates/shirabe-external-packages/src/symfony/console/helper/table_cell_style.rs index b48b6518..df24b404 100644 --- a/crates/shirabe-external-packages/src/symfony/console/helper/table_cell_style.rs +++ b/crates/shirabe-external-packages/src/symfony/console/helper/table_cell_style.rs @@ -52,15 +52,10 @@ impl TableCellStyle { .cloned() .collect(); if !diff.is_empty() { - return Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!( - "The TableCellStyle does not support the following options: '{}'.", - diff.join("', '"), - ), - code: 0, - }, - )); + return Err(InvalidArgumentException::new(format!( + "The TableCellStyle does not support the following options: '{}'.", + diff.join("', '"), + ))); } if let Some(align) = options.get("align") { @@ -69,15 +64,10 @@ impl TableCellStyle { _ => String::new(), }; if align_map(&align).is_none() { - return Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!( - "Wrong align value. Value must be following: '{}'.", - align_map_keys().join("', '"), - ), - code: 0, - }, - )); + return Err(InvalidArgumentException::new(format!( + "Wrong align value. Value must be following: '{}'.", + align_map_keys().join("', '"), + ))); } } diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/table_style.rs b/crates/shirabe-external-packages/src/symfony/console/helper/table_style.rs index a215b989..bbe899af 100644 --- a/crates/shirabe-external-packages/src/symfony/console/helper/table_style.rs +++ b/crates/shirabe-external-packages/src/symfony/console/helper/table_style.rs @@ -70,10 +70,9 @@ impl TableStyle { padding_char: String, ) -> anyhow::Result<Result<&mut Self, LogicException>> { if padding_char.is_empty() { - return Ok(Err(LogicException(shirabe_php_shim::LogicException { - message: "The padding char must not be empty.".to_string(), - code: 0, - }))); + return Ok(Err(LogicException::new( + "The padding char must not be empty.".to_string(), + ))); } self.padding_char = padding_char; @@ -254,13 +253,8 @@ impl TableStyle { ] .contains(&pad_type) { - return Ok(Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: "Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH)." - .to_string(), - code: 0, - }, - ))); + return Ok(Err(InvalidArgumentException::new("Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH)." + .to_string()))); } self.pad_type = pad_type; diff --git a/crates/shirabe-external-packages/src/symfony/console/input/argv_input.rs b/crates/shirabe-external-packages/src/symfony/console/input/argv_input.rs index fdc2dcc2..65ab9053 100644 --- a/crates/shirabe-external-packages/src/symfony/console/input/argv_input.rs +++ b/crates/shirabe-external-packages/src/symfony/console/input/argv_input.rs @@ -169,10 +169,10 @@ impl ArgvInput { shirabe_php_shim::mb_substr(name, i, Some(1), Some(&encoding)) } }; - return Err(RuntimeException(shirabe_php_shim::RuntimeException { - message: format!("The \"-{}\" option does not exist.", bad), - code: 0, - }) + return Err(RuntimeException::new(format!( + "The \"-{}\" option does not exist.", + bad + )) .into()); } @@ -292,9 +292,7 @@ impl ArgvInput { format!("No arguments expected, got \"{}\".", token) }; - return Err( - RuntimeException(shirabe_php_shim::RuntimeException { message, code: 0 }).into(), - ); + return Err(RuntimeException::new(message).into()); } Ok(()) @@ -303,10 +301,10 @@ impl ArgvInput { /// Adds a short option value. fn add_short_option(&mut self, shortcut: &str, value: PhpMixed) -> anyhow::Result<()> { if !self.inner.definition.has_shortcut(shortcut) { - return Err(RuntimeException(shirabe_php_shim::RuntimeException { - message: format!("The \"-{}\" option does not exist.", shortcut), - code: 0, - }) + return Err(RuntimeException::new(format!( + "The \"-{}\" option does not exist.", + shortcut + )) .into()); } @@ -323,19 +321,19 @@ impl ArgvInput { fn add_long_option(&mut self, name: &str, mut value: PhpMixed) -> anyhow::Result<()> { if !self.inner.definition.has_option(name) { if !self.inner.definition.has_negation(name) { - return Err(RuntimeException(shirabe_php_shim::RuntimeException { - message: format!("The \"--{}\" option does not exist.", name), - code: 0, - }) + return Err(RuntimeException::new(format!( + "The \"--{}\" option does not exist.", + name + )) .into()); } let option_name = self.inner.definition.negation_to_name(name)?; if !matches!(value, PhpMixed::Null) { - return Err(RuntimeException(shirabe_php_shim::RuntimeException { - message: format!("The \"--{}\" option does not accept a value.", name), - code: 0, - }) + return Err(RuntimeException::new(format!( + "The \"--{}\" option does not accept a value.", + name + )) .into()); } self.inner @@ -348,10 +346,10 @@ impl ArgvInput { let option = self.inner.definition.get_option(name)?; if !matches!(value, PhpMixed::Null) && !option.accept_value() { - return Err(RuntimeException(shirabe_php_shim::RuntimeException { - message: format!("The \"--{}\" option does not accept a value.", name), - code: 0, - }) + return Err(RuntimeException::new(format!( + "The \"--{}\" option does not accept a value.", + name + )) .into()); } @@ -373,10 +371,10 @@ impl ArgvInput { if matches!(value, PhpMixed::Null) { if option.is_value_required() { - return Err(RuntimeException(shirabe_php_shim::RuntimeException { - message: format!("The \"--{}\" option requires a value.", name), - code: 0, - }) + return Err(RuntimeException::new(format!( + "The \"--{}\" option requires a value.", + name + )) .into()); } diff --git a/crates/shirabe-external-packages/src/symfony/console/input/array_input.rs b/crates/shirabe-external-packages/src/symfony/console/input/array_input.rs index 9a19b839..de89a340 100644 --- a/crates/shirabe-external-packages/src/symfony/console/input/array_input.rs +++ b/crates/shirabe-external-packages/src/symfony/console/input/array_input.rs @@ -154,11 +154,9 @@ impl ArrayInput { /// Adds a short option value. fn add_short_option(&mut self, shortcut: &str, value: PhpMixed) -> anyhow::Result<()> { if !self.inner.definition.has_shortcut(shortcut) { - return Err(InvalidOptionException(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!("The \"-{}\" option does not exist.", shortcut), - code: 0, - }, + return Err(InvalidOptionException::new(format!( + "The \"-{}\" option does not exist.", + shortcut )) .into()); } @@ -176,11 +174,9 @@ impl ArrayInput { fn add_long_option(&mut self, name: &str, mut value: PhpMixed) -> anyhow::Result<()> { if !self.inner.definition.has_option(name) { if !self.inner.definition.has_negation(name) { - return Err(InvalidOptionException(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!("The \"--{}\" option does not exist.", name), - code: 0, - }, + return Err(InvalidOptionException::new(format!( + "The \"--{}\" option does not exist.", + name )) .into()); } @@ -197,11 +193,9 @@ impl ArrayInput { if matches!(value, PhpMixed::Null) { if option.is_value_required() { - return Err(InvalidOptionException(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!("The \"--{}\" option requires a value.", name), - code: 0, - }, + return Err(InvalidOptionException::new(format!( + "The \"--{}\" option requires a value.", + name )) .into()); } @@ -219,13 +213,11 @@ impl ArrayInput { /// Adds an argument value. fn add_argument(&mut self, name: &PhpMixed, value: PhpMixed) -> anyhow::Result<()> { if !self.inner.definition.has_argument(name) { - return Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: format!("The \"{}\" argument does not exist.", name.clone()), - code: 0, - }) - .into(), - ); + return Err(InvalidArgumentException::new(format!( + "The \"{}\" argument does not exist.", + name.clone() + )) + .into()); } self.inner diff --git a/crates/shirabe-external-packages/src/symfony/console/input/input.rs b/crates/shirabe-external-packages/src/symfony/console/input/input.rs index e14ed354..ceff85f6 100644 --- a/crates/shirabe-external-packages/src/symfony/console/input/input.rs +++ b/crates/shirabe-external-packages/src/symfony/console/input/input.rs @@ -80,13 +80,10 @@ impl Input { ); if !missing_arguments.is_empty() { - return Err(RuntimeException(shirabe_php_shim::RuntimeException { - message: format!( - "Not enough arguments (missing: \"{}\").", - shirabe_php_shim::implode(", ", &missing_arguments), - ), - code: 0, - }) + return Err(RuntimeException::new(format!( + "Not enough arguments (missing: \"{}\").", + shirabe_php_shim::implode(", ", &missing_arguments), + )) .into()); } @@ -113,13 +110,11 @@ impl Input { .definition .has_argument(&PhpMixed::String(name.to_string())) { - return Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: format!("The \"{}\" argument does not exist.", name), - code: 0, - }) - .into(), - ); + return Err(InvalidArgumentException::new(format!( + "The \"{}\" argument does not exist.", + name + )) + .into()); } Ok(match self.arguments.get(name) { @@ -137,13 +132,11 @@ impl Input { .definition .has_argument(&PhpMixed::String(name.to_string())) { - return Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: format!("The \"{}\" argument does not exist.", name), - code: 0, - }) - .into(), - ); + return Err(InvalidArgumentException::new(format!( + "The \"{}\" argument does not exist.", + name + )) + .into()); } self.arguments.insert(name.to_string(), value); @@ -174,13 +167,11 @@ impl Input { } if !self.definition.has_option(name) { - return Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: format!("The \"{}\" option does not exist.", name), - code: 0, - }) - .into(), - ); + return Err(InvalidArgumentException::new(format!( + "The \"{}\" option does not exist.", + name + )) + .into()); } Ok(if self.options.contains_key(name) { @@ -198,13 +189,11 @@ impl Input { return Ok(()); } else if !self.definition.has_option(name) { - return Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: format!("The \"{}\" option does not exist.", name), - code: 0, - }) - .into(), - ); + return Err(InvalidArgumentException::new(format!( + "The \"{}\" option does not exist.", + name + )) + .into()); } self.options.insert(name.to_string(), value); diff --git a/crates/shirabe-external-packages/src/symfony/console/input/input_argument.rs b/crates/shirabe-external-packages/src/symfony/console/input/input_argument.rs index 4a732bd8..35f62389 100644 --- a/crates/shirabe-external-packages/src/symfony/console/input/input_argument.rs +++ b/crates/shirabe-external-packages/src/symfony/console/input/input_argument.rs @@ -26,13 +26,11 @@ impl InputArgument { let mode = match mode { None => Self::OPTIONAL, Some(m) if !(1..=7).contains(&m) => { - return Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: format!("Argument mode \"{}\" is not valid.", m), - code: 0, - }) - .into(), - ); + return Err(InvalidArgumentException::new(format!( + "Argument mode \"{}\" is not valid.", + m + )) + .into()); } Some(m) => m, }; @@ -63,11 +61,9 @@ impl InputArgument { pub fn set_default(&mut self, default: PhpMixed) -> anyhow::Result<()> { if self.is_required() && !matches!(default, PhpMixed::Null) { - return Err(LogicException(shirabe_php_shim::LogicException { - message: "Cannot set a default value except for InputArgument::OPTIONAL mode." - .to_string(), - code: 0, - }) + return Err(LogicException::new( + "Cannot set a default value except for InputArgument::OPTIONAL mode.".to_string(), + ) .into()); } @@ -76,11 +72,9 @@ impl InputArgument { PhpMixed::Null => PhpMixed::List(vec![]), PhpMixed::List(_) => default, _ => { - return Err(LogicException(shirabe_php_shim::LogicException { - message: "A default value for an array argument must be an array." - .to_string(), - code: 0, - }) + return Err(LogicException::new( + "A default value for an array argument must be an array.".to_string(), + ) .into()); } } diff --git a/crates/shirabe-external-packages/src/symfony/console/input/input_definition.rs b/crates/shirabe-external-packages/src/symfony/console/input/input_definition.rs index eabb7003..bd32a8f9 100644 --- a/crates/shirabe-external-packages/src/symfony/console/input/input_definition.rs +++ b/crates/shirabe-external-packages/src/symfony/console/input/input_definition.rs @@ -110,39 +110,30 @@ impl InputDefinition { let argument = std::rc::Rc::new(argument); if self.arguments.contains_key(argument.get_name()) { - return Err(LogicException(shirabe_php_shim::LogicException { - message: format!( - "An argument with name \"{}\" already exists.", - argument.get_name(), - ), - code: 0, - }) + return Err(LogicException::new(format!( + "An argument with name \"{}\" already exists.", + argument.get_name(), + )) .into()); } if let Some(last_array_argument) = &self.last_array_argument { - return Err(LogicException(shirabe_php_shim::LogicException { - message: format!( - "Cannot add a required argument \"{}\" after an array argument \"{}\".", - argument.get_name(), - last_array_argument.get_name(), - ), - code: 0, - }) + return Err(LogicException::new(format!( + "Cannot add a required argument \"{}\" after an array argument \"{}\".", + argument.get_name(), + last_array_argument.get_name(), + )) .into()); } if argument.is_required() && let Some(last_optional_argument) = &self.last_optional_argument { - return Err(LogicException(shirabe_php_shim::LogicException { - message: format!( - "Cannot add a required argument \"{}\" after an optional one \"{}\".", - argument.get_name(), - last_optional_argument.get_name(), - ), - code: 0, - }) + return Err(LogicException::new(format!( + "Cannot add a required argument \"{}\" after an optional one \"{}\".", + argument.get_name(), + last_optional_argument.get_name(), + )) .into()); } @@ -165,13 +156,11 @@ impl InputDefinition { /// Returns an InputArgument by name or by position. pub fn get_argument(&self, name: &PhpMixed) -> anyhow::Result<std::rc::Rc<InputArgument>> { if !self.has_argument(name) { - return Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: format!("The \"{}\" argument does not exist.", name.clone()), - code: 0, - }) - .into(), - ); + return Err(InvalidArgumentException::new(format!( + "The \"{}\" argument does not exist.", + name.clone() + )) + .into()); } match name { @@ -260,17 +249,17 @@ impl InputDefinition { if let Some(existing) = self.options.get(option.get_name()) && !option.equals(existing) { - return Err(LogicException(shirabe_php_shim::LogicException { - message: format!("An option named \"{}\" already exists.", option.get_name()), - code: 0, - }) + return Err(LogicException::new(format!( + "An option named \"{}\" already exists.", + option.get_name() + )) .into()); } if self.negations.contains_key(option.get_name()) { - return Err(LogicException(shirabe_php_shim::LogicException { - message: format!("An option named \"{}\" already exists.", option.get_name()), - code: 0, - }) + return Err(LogicException::new(format!( + "An option named \"{}\" already exists.", + option.get_name() + )) .into()); } @@ -279,13 +268,10 @@ impl InputDefinition { if let Some(existing_name) = self.shortcuts.get(&shortcut) && !option.equals(&self.options[existing_name]) { - return Err(LogicException(shirabe_php_shim::LogicException { - message: format!( - "An option with shortcut \"{}\" already exists.", - shortcut.clone(), - ), - code: 0, - }) + return Err(LogicException::new(format!( + "An option with shortcut \"{}\" already exists.", + shortcut.clone(), + )) .into()); } } @@ -303,10 +289,10 @@ impl InputDefinition { if option.is_negatable() { let negated_name = format!("no-{}", option.get_name()); if self.options.contains_key(&negated_name) { - return Err(LogicException(shirabe_php_shim::LogicException { - message: format!("An option named \"{}\" already exists.", negated_name), - code: 0, - }) + return Err(LogicException::new(format!( + "An option named \"{}\" already exists.", + negated_name + )) .into()); } self.negations @@ -319,13 +305,11 @@ impl InputDefinition { /// Returns an InputOption by name. pub fn get_option(&self, name: &str) -> anyhow::Result<std::rc::Rc<InputOption>> { if !self.has_option(name) { - return Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: format!("The \"--{}\" option does not exist.", name), - code: 0, - }) - .into(), - ); + return Err(InvalidArgumentException::new(format!( + "The \"--{}\" option does not exist.", + name + )) + .into()); } Ok(std::rc::Rc::clone(&self.options[name])) @@ -374,13 +358,11 @@ impl InputDefinition { /// Returns the InputOption name given a shortcut. pub fn shortcut_to_name(&self, shortcut: &str) -> anyhow::Result<String> { match self.shortcuts.get(shortcut) { - None => Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: format!("The \"-{}\" option does not exist.", shortcut), - code: 0, - }) - .into(), - ), + None => Err(InvalidArgumentException::new(format!( + "The \"-{}\" option does not exist.", + shortcut + )) + .into()), Some(name) => Ok(name.clone()), } } @@ -388,13 +370,11 @@ impl InputDefinition { /// Returns the InputOption name given a negation. pub fn negation_to_name(&self, negation: &str) -> anyhow::Result<String> { match self.negations.get(negation) { - None => Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: format!("The \"--{}\" option does not exist.", negation), - code: 0, - }) - .into(), - ), + None => Err(InvalidArgumentException::new(format!( + "The \"--{}\" option does not exist.", + negation + )) + .into()), Some(name) => Ok(name.clone()), } } diff --git a/crates/shirabe-external-packages/src/symfony/console/input/input_option.rs b/crates/shirabe-external-packages/src/symfony/console/input/input_option.rs index 3f8fdeaf..79eff582 100644 --- a/crates/shirabe-external-packages/src/symfony/console/input/input_option.rs +++ b/crates/shirabe-external-packages/src/symfony/console/input/input_option.rs @@ -34,13 +34,10 @@ impl InputOption { }; if name.is_empty() { - return Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: "An option name cannot be empty.".to_string(), - code: 0, - }) - .into(), - ); + return Err(InvalidArgumentException::new( + "An option name cannot be empty.".to_string(), + ) + .into()); } let shortcut = match shortcut { @@ -69,13 +66,11 @@ impl InputOption { let mode = match mode { None => Self::VALUE_NONE, Some(m) if !(1..(Self::VALUE_NEGATABLE << 1)).contains(&m) => { - return Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: format!("Option mode \"{}\" is not valid.", m), - code: 0, - }) - .into(), - ); + return Err(InvalidArgumentException::new(format!( + "Option mode \"{}\" is not valid.", + m + )) + .into()); } Some(m) => m, }; @@ -89,17 +84,11 @@ impl InputOption { }; if option.is_array() && !option.accept_value() { - return Err(InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: "Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.".to_string(), - code: 0, - }) + return Err(InvalidArgumentException::new("Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.".to_string()) .into()); } if option.is_negatable() && option.accept_value() { - return Err(InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: "Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.".to_string(), - code: 0, - }) + return Err(InvalidArgumentException::new("Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.".to_string()) .into()); } @@ -115,13 +104,10 @@ impl InputOption { shirabe_php_shim::array_filter(&parts, |s: &String| !s.is_empty()); let result = shirabe_php_shim::implode("|", &filtered); if result.is_empty() { - return Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: "An option shortcut cannot be empty.".to_string(), - code: 0, - }) - .into(), - ); + return Err(InvalidArgumentException::new( + "An option shortcut cannot be empty.".to_string(), + ) + .into()); } Ok(Some(result)) } @@ -157,11 +143,9 @@ impl InputOption { pub fn set_default(&mut self, default: PhpMixed) -> anyhow::Result<()> { if Self::VALUE_NONE == (Self::VALUE_NONE & self.mode) && !matches!(default, PhpMixed::Null) { - return Err(LogicException(shirabe_php_shim::LogicException { - message: "Cannot set a default value when using InputOption::VALUE_NONE mode." - .to_string(), - code: 0, - }) + return Err(LogicException::new( + "Cannot set a default value when using InputOption::VALUE_NONE mode.".to_string(), + ) .into()); } @@ -171,11 +155,9 @@ impl InputOption { // PHP `is_array()` accepts both list-style and associative arrays. PhpMixed::List(_) | PhpMixed::Array(_) => default, _ => { - return Err(LogicException(shirabe_php_shim::LogicException { - message: "A default value for an array option must be an array." - .to_string(), - code: 0, - }) + return Err(LogicException::new( + "A default value for an array option must be an array.".to_string(), + ) .into()); } } diff --git a/crates/shirabe-external-packages/src/symfony/console/input/string_input.rs b/crates/shirabe-external-packages/src/symfony/console/input/string_input.rs index 3b3c3207..0ca8c975 100644 --- a/crates/shirabe-external-packages/src/symfony/console/input/string_input.rs +++ b/crates/shirabe-external-packages/src/symfony/console/input/string_input.rs @@ -127,16 +127,11 @@ impl StringInput { shirabe_php_shim::strlen(m[&CaptureKey::ByIndex(0)].as_deref().unwrap_or("")); } else { // should never happen - return Err( - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: format!( - "Unable to parse input near \"... {} ...\".", - shirabe_php_shim::substr(input, cursor, Some(10)), - ), - code: 0, - }) - .into(), - ); + return Err(InvalidArgumentException::new(format!( + "Unable to parse input near \"... {} ...\".", + shirabe_php_shim::substr(input, cursor, Some(10)), + )) + .into()); } } diff --git a/crates/shirabe-external-packages/src/symfony/console/output/stream_output.rs b/crates/shirabe-external-packages/src/symfony/console/output/stream_output.rs index 4d8eb921..9140dbc8 100644 --- a/crates/shirabe-external-packages/src/symfony/console/output/stream_output.rs +++ b/crates/shirabe-external-packages/src/symfony/console/output/stream_output.rs @@ -37,12 +37,8 @@ impl StreamOutput { let verbosity = verbosity.unwrap_or(VERBOSITY_NORMAL); if shirabe_php_shim::get_resource_type(&stream) != "stream" { - return Ok(Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: "The StreamOutput class needs a stream as its first argument." - .to_string(), - code: 0, - }, + return Ok(Err(InvalidArgumentException::new( + "The StreamOutput class needs a stream as its first argument.".to_string(), ))); } diff --git a/crates/shirabe-external-packages/src/symfony/console/output/trimmed_buffer_output.rs b/crates/shirabe-external-packages/src/symfony/console/output/trimmed_buffer_output.rs index b42f2a4e..4eb91b41 100644 --- a/crates/shirabe-external-packages/src/symfony/console/output/trimmed_buffer_output.rs +++ b/crates/shirabe-external-packages/src/symfony/console/output/trimmed_buffer_output.rs @@ -21,16 +21,10 @@ impl TrimmedBufferOutput { formatter: Option<std::rc::Rc<std::cell::RefCell<dyn OutputFormatterInterface>>>, ) -> Result<Self, InvalidArgumentException> { if max_length <= 0 { - return Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!( - "\"{}()\" expects a strictly positive maxLength. Got {}.", - "Symfony\\Component\\Console\\Output\\TrimmedBufferOutput::__construct", - max_length, - ), - code: 0, - }, - )); + return Err(InvalidArgumentException::new(format!( + "\"{}()\" expects a strictly positive maxLength. Got {}.", + "Symfony\\Component\\Console\\Output\\TrimmedBufferOutput::__construct", max_length, + ))); } Ok(Self { diff --git a/crates/shirabe-external-packages/src/symfony/console/question/choice_question.rs b/crates/shirabe-external-packages/src/symfony/console/question/choice_question.rs index 9deec851..52316cf4 100644 --- a/crates/shirabe-external-packages/src/symfony/console/question/choice_question.rs +++ b/crates/shirabe-external-packages/src/symfony/console/question/choice_question.rs @@ -27,10 +27,9 @@ impl ChoiceQuestion { default: Option<PhpMixed>, ) -> Result<Self, LogicException> { if choices.is_empty() { - return Err(LogicException(shirabe_php_shim::LogicException { - message: "Choice question must have at least 1 choice available.".to_string(), - code: 0, - })); + return Err(LogicException::new( + "Choice question must have at least 1 choice available.".to_string(), + )); } let mut this = Self { @@ -129,15 +128,10 @@ impl ChoiceQuestion { &shirabe_php_shim::strval(&selected), &mut matches, ) { - return Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: shirabe_php_shim::sprintf( - &error_message, - std::slice::from_ref(&selected), - ), - code: 0, - }, - )); + return Err(InvalidArgumentException::new(shirabe_php_shim::sprintf( + &error_message, + std::slice::from_ref(&selected), + ))); } shirabe_php_shim::explode(",", &shirabe_php_shim::strval(&selected)) @@ -168,15 +162,10 @@ impl ChoiceQuestion { } if results.len() > 1 { - return Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: format!( - "The provided answer is ambiguous. Value should be one of \"{}\".", - shirabe_php_shim::implode("\" or \"", &results), - ), - code: 0, - }, - )); + return Err(InvalidArgumentException::new(format!( + "The provided answer is ambiguous. Value should be one of \"{}\".", + shirabe_php_shim::implode("\" or \"", &results), + ))); } // array_search($value, $choices) @@ -210,15 +199,10 @@ impl ChoiceQuestion { // false === $result if matches!(result, PhpMixed::Bool(false)) { - return Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: shirabe_php_shim::sprintf( - &error_message, - std::slice::from_ref(value), - ), - code: 0, - }, - )); + return Err(InvalidArgumentException::new(shirabe_php_shim::sprintf( + &error_message, + std::slice::from_ref(value), + ))); } // For associative choices, consistently return the key as string: diff --git a/crates/shirabe-external-packages/src/symfony/console/question/question.rs b/crates/shirabe-external-packages/src/symfony/console/question/question.rs index a96c3159..165f287e 100644 --- a/crates/shirabe-external-packages/src/symfony/console/question/question.rs +++ b/crates/shirabe-external-packages/src/symfony/console/question/question.rs @@ -127,10 +127,9 @@ impl Question { /// Throws LogicException in case the autocompleter is also used. pub fn set_hidden(&mut self, hidden: bool) -> Result<&mut Self, LogicException> { if self.autocompleter_callback.is_some() { - return Err(LogicException(shirabe_php_shim::LogicException { - message: "A hidden question cannot use the autocompleter.".to_string(), - code: 0, - })); + return Err(LogicException::new( + "A hidden question cannot use the autocompleter.".to_string(), + )); } self.hidden = hidden; @@ -225,10 +224,9 @@ impl Question { callback: Option<Box<dyn Fn(&str) -> Option<Vec<PhpMixed>>>>, ) -> Result<&mut Self, LogicException> { if self.hidden && callback.is_some() { - return Err(LogicException(shirabe_php_shim::LogicException { - message: "A hidden question cannot use the autocompleter.".to_string(), - code: 0, - })); + return Err(LogicException::new( + "A hidden question cannot use the autocompleter.".to_string(), + )); } self.autocompleter_callback = callback; @@ -267,11 +265,8 @@ impl Question { if let Some(attempts) = attempts && attempts < 1 { - return Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: "Maximum number of attempts must be a positive value.".to_string(), - code: 0, - }, + return Err(InvalidArgumentException::new( + "Maximum number of attempts must be a positive value.".to_string(), )); } diff --git a/crates/shirabe-external-packages/src/symfony/console/style/symfony_style.rs b/crates/shirabe-external-packages/src/symfony/console/style/symfony_style.rs index b62c2565..e0994e99 100644 --- a/crates/shirabe-external-packages/src/symfony/console/style/symfony_style.rs +++ b/crates/shirabe-external-packages/src/symfony/console/style/symfony_style.rs @@ -469,12 +469,7 @@ impl SymfonyStyle { ) -> Option<Box<dyn Fn(Option<PhpMixed>) -> Result<PhpMixed, InvalidArgumentException>>> { validator.map(|validator| { Box::new(move |value: Option<PhpMixed>| { - validator(value).map_err(|e| { - InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { - message: e.to_string(), - code: 0, - }) - }) + validator(value).map_err(|e| InvalidArgumentException::new(e.to_string())) }) as Box<dyn Fn(Option<PhpMixed>) -> Result<PhpMixed, InvalidArgumentException>> }) 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 e666e9ea..ffe80ea1 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 @@ -1,9 +1,10 @@ //! ref: composer/vendor/symfony/filesystem/Exception/IOException.php +use shirabe_php_shim::RuntimeException; + #[derive(Debug)] pub struct IOException { - pub message: String, - pub code: i64, + inner: RuntimeException, pub path: Option<String>, } @@ -11,21 +12,18 @@ impl IOException { pub fn new( message: String, code: i64, - _previous: Option<Box<dyn std::error::Error + Send + Sync>>, + previous: Option<std::sync::Arc<shirabe_php_shim::AnyThrowable>>, path: Option<String>, ) -> Self { Self { - message, - code, + inner: RuntimeException::with_code_and_previous(message, code, previous), path, } } } -impl std::fmt::Display for IOException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.message) - } -} - -impl std::error::Error for IOException {} +shirabe_php_shim::impl_php_exception!( + IOException, + inner, + r"Symfony\Component\Filesystem\Exception\IOException" +); diff --git a/crates/shirabe-external-packages/src/symfony/filesystem/filesystem.rs b/crates/shirabe-external-packages/src/symfony/filesystem/filesystem.rs index b1245a61..8834f724 100644 --- a/crates/shirabe-external-packages/src/symfony/filesystem/filesystem.rs +++ b/crates/shirabe-external-packages/src/symfony/filesystem/filesystem.rs @@ -420,7 +420,7 @@ impl Filesystem { shirabe_php_shim::SKIP_DOTS }; let dir = shirabe_php_shim::recursive_directory_iterator(&origin_dir, flags) - .map_err(|e| anyhow::anyhow!("{}", e.message))?; + .map_err(|e| anyhow::anyhow!("{}", e.get_message()))?; let iterator = shirabe_php_shim::recursive_iterator_iterator( dir, shirabe_php_shim::RecursiveIteratorIterator::SELF_FIRST, diff --git a/crates/shirabe-external-packages/src/symfony/process/exception/invalid_argument_exception.rs b/crates/shirabe-external-packages/src/symfony/process/exception/invalid_argument_exception.rs index b70a1d39..c9a42653 100644 --- a/crates/shirabe-external-packages/src/symfony/process/exception/invalid_argument_exception.rs +++ b/crates/shirabe-external-packages/src/symfony/process/exception/invalid_argument_exception.rs @@ -2,20 +2,19 @@ #[derive(Debug)] pub struct InvalidArgumentException { - pub message: String, - pub code: i64, + inner: shirabe_php_shim::InvalidArgumentException, } impl InvalidArgumentException { pub fn new(message: String) -> Self { - Self { message, code: 0 } + Self { + inner: shirabe_php_shim::InvalidArgumentException::new(message), + } } } -impl std::fmt::Display for InvalidArgumentException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.message) - } -} - -impl std::error::Error for InvalidArgumentException {} +shirabe_php_shim::impl_php_exception!( + InvalidArgumentException, + inner, + r"Symfony\Component\Process\Exception\InvalidArgumentException" +); diff --git a/crates/shirabe-external-packages/src/symfony/process/exception/logic_exception.rs b/crates/shirabe-external-packages/src/symfony/process/exception/logic_exception.rs index c9a34661..36e9bcca 100644 --- a/crates/shirabe-external-packages/src/symfony/process/exception/logic_exception.rs +++ b/crates/shirabe-external-packages/src/symfony/process/exception/logic_exception.rs @@ -2,20 +2,19 @@ #[derive(Debug)] pub struct LogicException { - pub message: String, - pub code: i64, + inner: shirabe_php_shim::LogicException, } impl LogicException { pub fn new(message: String) -> Self { - Self { message, code: 0 } + Self { + inner: shirabe_php_shim::LogicException::new(message), + } } } -impl std::fmt::Display for LogicException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.message) - } -} - -impl std::error::Error for LogicException {} +shirabe_php_shim::impl_php_exception!( + LogicException, + inner, + r"Symfony\Component\Process\Exception\LogicException" +); diff --git a/crates/shirabe-external-packages/src/symfony/process/exception/process_failed_exception.rs b/crates/shirabe-external-packages/src/symfony/process/exception/process_failed_exception.rs index b99e876a..e5d6d7fc 100644 --- a/crates/shirabe-external-packages/src/symfony/process/exception/process_failed_exception.rs +++ b/crates/shirabe-external-packages/src/symfony/process/exception/process_failed_exception.rs @@ -1,12 +1,12 @@ //! ref: composer/vendor/symfony/process/Exception/ProcessFailedException.php use crate::symfony::process::exception::invalid_argument_exception::InvalidArgumentException; +use crate::symfony::process::exception::runtime_exception::RuntimeException; use crate::symfony::process::process::Process; #[derive(Debug)] pub struct ProcessFailedException { - pub message: String, - pub code: i64, + inner: RuntimeException, } impl ProcessFailedException { @@ -36,16 +36,13 @@ impl ProcessFailedException { ); Ok(Self { - message: error, - code: 0, + inner: RuntimeException::new(error), }) } } -impl std::fmt::Display for ProcessFailedException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.message) - } -} - -impl std::error::Error for ProcessFailedException {} +shirabe_php_shim::impl_php_exception!( + ProcessFailedException, + inner, + r"Symfony\Component\Process\Exception\ProcessFailedException" +); diff --git a/crates/shirabe-external-packages/src/symfony/process/exception/process_signaled_exception.rs b/crates/shirabe-external-packages/src/symfony/process/exception/process_signaled_exception.rs index 03c4df27..9f325750 100644 --- a/crates/shirabe-external-packages/src/symfony/process/exception/process_signaled_exception.rs +++ b/crates/shirabe-external-packages/src/symfony/process/exception/process_signaled_exception.rs @@ -1,11 +1,11 @@ //! ref: composer/vendor/symfony/process/Exception/ProcessSignaledException.php +use crate::symfony::process::exception::runtime_exception::RuntimeException; use crate::symfony::process::process::Process; #[derive(Debug)] pub struct ProcessSignaledException { - pub message: String, - pub code: i64, + inner: RuntimeException, signal: i64, } @@ -14,8 +14,10 @@ impl ProcessSignaledException { let signal = process.get_term_signal()?; Ok(Self { - message: format!("The process has been signaled with signal \"{}\".", signal), - code: 0, + inner: RuntimeException::new(format!( + "The process has been signaled with signal \"{}\".", + signal + )), signal, }) } @@ -25,10 +27,8 @@ impl ProcessSignaledException { } } -impl std::fmt::Display for ProcessSignaledException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.message) - } -} - -impl std::error::Error for ProcessSignaledException {} +shirabe_php_shim::impl_php_exception!( + ProcessSignaledException, + inner, + r"Symfony\Component\Process\Exception\ProcessSignaledException" +); diff --git a/crates/shirabe-external-packages/src/symfony/process/exception/process_timed_out_exception.rs b/crates/shirabe-external-packages/src/symfony/process/exception/process_timed_out_exception.rs index 46564c37..9485d1c2 100644 --- a/crates/shirabe-external-packages/src/symfony/process/exception/process_timed_out_exception.rs +++ b/crates/shirabe-external-packages/src/symfony/process/exception/process_timed_out_exception.rs @@ -1,11 +1,11 @@ //! ref: composer/vendor/symfony/process/Exception/ProcessTimedOutException.php +use crate::symfony::process::exception::runtime_exception::RuntimeException; use crate::symfony::process::process::Process; #[derive(Debug)] pub struct ProcessTimedOutException { - pub message: String, - pub code: i64, + inner: RuntimeException, } impl ProcessTimedOutException { @@ -18,14 +18,14 @@ impl ProcessTimedOutException { exceeded_timeout.map(|t| t.to_string()).unwrap_or_default(), ); - Self { message, code: 0 } + Self { + inner: RuntimeException::new(message), + } } } -impl std::fmt::Display for ProcessTimedOutException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.message) - } -} - -impl std::error::Error for ProcessTimedOutException {} +shirabe_php_shim::impl_php_exception!( + ProcessTimedOutException, + inner, + r"Symfony\Component\Process\Exception\ProcessTimedOutException" +); diff --git a/crates/shirabe-external-packages/src/symfony/process/exception/runtime_exception.rs b/crates/shirabe-external-packages/src/symfony/process/exception/runtime_exception.rs index 42422aff..e9cc31e0 100644 --- a/crates/shirabe-external-packages/src/symfony/process/exception/runtime_exception.rs +++ b/crates/shirabe-external-packages/src/symfony/process/exception/runtime_exception.rs @@ -2,20 +2,19 @@ #[derive(Debug)] pub struct RuntimeException { - pub message: String, - pub code: i64, + inner: shirabe_php_shim::RuntimeException, } impl RuntimeException { pub fn new(message: String) -> Self { - Self { message, code: 0 } + Self { + inner: shirabe_php_shim::RuntimeException::new(message), + } } } -impl std::fmt::Display for RuntimeException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.message) - } -} - -impl std::error::Error for RuntimeException {} +shirabe_php_shim::impl_php_exception!( + RuntimeException, + inner, + r"Symfony\Component\Process\Exception\RuntimeException" +); diff --git a/crates/shirabe-php-shim/src/exception.rs b/crates/shirabe-php-shim/src/exception.rs index c57a3451..803d5b64 100644 --- a/crates/shirabe-php-shim/src/exception.rs +++ b/crates/shirabe-php-shim/src/exception.rs @@ -1,133 +1,416 @@ -use crate::PharException; +use crate::PhpClass; -#[derive(Debug)] -pub struct Exception { - pub message: String, - pub code: i64, +/// The fields a PHP `\Throwable` carries: its message and code, and the exception it wraps. Ported +/// exception types embed this, either directly or through the parent exception they extend. +#[derive(Debug, Clone)] +pub struct ThrowableFields { + message: String, + code: i64, + previous: Option<std::sync::Arc<AnyThrowable>>, } -impl std::fmt::Display for Exception { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.message) +impl ThrowableFields { + pub fn get_message(&self) -> &str { + &self.message } -} -impl std::error::Error for Exception {} + pub fn get_code(&self) -> i64 { + self.code + } -#[derive(Debug)] -pub struct RuntimeException { - pub message: String, - pub code: i64, + /// PHP's `code` property is protected with no setter; Composer writes it through reflection. + pub fn set_code(&mut self, code: i64) { + self.code = code; + } + + pub fn get_previous(&self) -> Option<&AnyThrowable> { + self.previous.as_deref() + } } -impl std::fmt::Display for RuntimeException { +impl std::fmt::Display for ThrowableFields { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.message) } } -impl std::error::Error for RuntimeException {} +crate::impl_php_class!(ThrowableFields, r"Throwable"); -#[derive(Debug)] -pub struct UnexpectedValueException { - pub message: String, - pub code: i64, +/// A ported PHP exception class, seen as the object PHP `throw`s: its [`ThrowableFields`], its +/// concrete Rust type, and the instance of its parent class it embeds. +/// [`impl_php_exception!`] implements this for every ported exception. +/// +/// Every ported exception embeds an instance of the class it extends, so [`Self::parent`] walks +/// exactly PHP's chain of superclasses and bottoms out at the [`ThrowableFields`]. +pub trait Throwable: + PhpClass + std::fmt::Debug + std::fmt::Display + Send + Sync + 'static +{ + fn fields(&self) -> &ThrowableFields; + fn as_any(&self) -> &(dyn std::any::Any + 'static); + fn as_any_mut(&mut self) -> &mut (dyn std::any::Any + 'static); + fn parent(&self) -> Option<&dyn Throwable>; + fn parent_mut(&mut self) -> Option<&mut dyn Throwable>; } -impl std::fmt::Display for UnexpectedValueException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.message) +impl Throwable for ThrowableFields { + fn fields(&self) -> &ThrowableFields { + self } -} -impl std::error::Error for UnexpectedValueException {} + fn as_any(&self) -> &(dyn std::any::Any + 'static) { + self + } -#[derive(Debug)] -pub struct InvalidArgumentException { - pub message: String, - pub code: i64, -} + fn as_any_mut(&mut self) -> &mut (dyn std::any::Any + 'static) { + self + } -impl std::fmt::Display for InvalidArgumentException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.message) + fn parent(&self) -> Option<&dyn Throwable> { + None } -} -impl std::error::Error for InvalidArgumentException {} + fn parent_mut(&mut self) -> Option<&mut dyn Throwable> { + None + } +} +/// The form a thrown PHP exception takes while it travels as a Rust error. Ported exception types +/// deliberately do not implement [`std::error::Error`], so this box is the only way one reaches an +/// `anyhow::Error`: an error either carries an `AnyThrowable`, and PHP would see a `\Throwable`, +/// or it does not, and PHP would see nothing catchable. +/// +/// This is what makes `catch (\RuntimeException $e)` portable: [`Catch::catch`] answers over the +/// whole class hierarchy, rather than over the Rust type, which is a leaf of it. #[derive(Debug)] -pub struct TypeError { - pub message: String, - pub code: i64, +pub struct AnyThrowable(Box<dyn Throwable>); + +impl AnyThrowable { + pub fn new(exception: impl Throwable) -> Self { + Self(Box::new(exception)) + } + + /// The exception a Rust error carries, or `None` if it carries none. + // TODO(phase-c): this matches only an error that *is* the exception, where [`Catch`]'s + // `anyhow::Error` impl also sees one behind an `anyhow::Context` layer. Nothing in the port + // adds context to an error yet, so an exception wrapped that way would go silently unseen. + pub fn of<'e>(error: &'e (dyn std::error::Error + 'static)) -> Option<&'e Self> { + error.downcast_ref::<Self>() + } + + /// 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. + fn downcast_ref<T: Throwable>(&self) -> Option<&T> { + let mut class: &dyn Throwable = &*self.0; + loop { + if let Some(instance) = class.as_any().downcast_ref::<T>() { + return Some(instance); + } + class = class.parent()?; + } + } + + /// [`AnyThrowable::downcast_ref`] for a caught exception that is about to be mutated, the way + /// PHP writes to a property of the object it caught. + fn downcast_mut<T: Throwable>(&mut self) -> Option<&mut T> { + let mut superclasses = 0; + let mut class: &dyn Throwable = &*self.0; + while !class.as_any().is::<T>() { + class = class.parent()?; + superclasses += 1; + } + + let mut class: &mut dyn Throwable = &mut *self.0; + for _ in 0..superclasses { + class = class.parent_mut().expect("walked immutably just above"); + } + class.as_any_mut().downcast_mut::<T>() + } + + pub fn get_message(&self) -> &str { + self.0.fields().get_message() + } + + pub fn get_code(&self) -> i64 { + self.0.fields().get_code() + } + + pub fn get_previous(&self) -> Option<&AnyThrowable> { + self.0.fields().get_previous() + } } -impl std::fmt::Display for TypeError { +impl std::fmt::Display for AnyThrowable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.message) + std::fmt::Display::fmt(&self.0, f) } } -impl std::error::Error for TypeError {} +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)) + } +} -#[derive(Debug)] -pub struct LogicException { - pub message: String, - pub code: i64, +impl PhpClass for AnyThrowable { + fn php_class_name(&self) -> String { + self.0.php_class_name() + } } -impl std::fmt::Display for LogicException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.message) +/// PHP's `catch` applied to a Rust error. +pub trait Catch { + /// The exception the error carries, seen as an instance of `T`, or `None` if it carries no + /// exception or one of an unrelated class. + fn catch<T: Throwable>(&self) -> Option<&T>; + + /// [`Catch::catch`] for a caught exception that is about to be mutated, the way PHP writes to + /// a property of the object it caught. + fn catch_mut<T: Throwable>(&mut self) -> Option<&mut T>; + + /// PHP's `$e instanceof T`. + fn is_instanceof<T: Throwable>(&self) -> bool { + self.catch::<T>().is_some() } + + /// PHP's `get_class($e) === T::class`: the class the exception was thrown as, rather than + /// [`Catch::is_instanceof`]'s walk over its superclasses. + fn is_class<T: Throwable>(&self) -> bool; } -impl std::error::Error for LogicException {} +impl Catch for anyhow::Error { + fn catch<T: Throwable>(&self) -> Option<&T> { + self.downcast_ref::<AnyThrowable>()?.downcast_ref::<T>() + } -#[derive(Debug)] -pub struct BadMethodCallException { - pub message: String, - pub code: i64, + fn catch_mut<T: Throwable>(&mut self) -> Option<&mut T> { + self.downcast_mut::<AnyThrowable>()?.downcast_mut::<T>() + } + + fn is_class<T: Throwable>(&self) -> bool { + self.downcast_ref::<AnyThrowable>() + .is_some_and(|e| e.is_class::<T>()) + } } -impl std::fmt::Display for BadMethodCallException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.message) +impl Catch for AnyThrowable { + fn catch<T: Throwable>(&self) -> Option<&T> { + self.downcast_ref::<T>() + } + + fn catch_mut<T: Throwable>(&mut self) -> Option<&mut T> { + self.downcast_mut::<T>() + } + + fn is_class<T: Throwable>(&self) -> bool { + self.0.as_any().is::<T>() } } -impl std::error::Error for BadMethodCallException {} +/// Implements the `\Throwable` surface for a ported exception type, given the field holding the +/// fields it inherits — a [`ThrowableFields`] for a type that extends a PHP built-in directly, or +/// the embedded parent exception otherwise — and the fully-qualified name of the PHP class. +/// +/// ```ignore +/// impl_php_exception!(SolverBugException, 0, r"Composer\DependencyResolver\SolverBugException"); +/// ``` +/// +/// The type is deliberately left without a [`std::error::Error`] impl, so that the only route from +/// it to an `anyhow::Error` is the [`AnyThrowable`] this generates a conversion to. +#[macro_export] +macro_rules! impl_php_exception { + ($ty:ty, $field:tt, $class_name:expr) => { + $crate::impl_php_exception!(@accessors $ty, $field, $class_name); -#[derive(Debug)] -pub struct OutOfBoundsException { - pub message: String, - pub code: i64, + impl $crate::Throwable for $ty { + fn fields(&self) -> &$crate::ThrowableFields { + $crate::Throwable::fields(&self.$field) + } + + fn as_any(&self) -> &(dyn std::any::Any + 'static) { + self + } + + fn as_any_mut(&mut self) -> &mut (dyn std::any::Any + 'static) { + self + } + + fn parent(&self) -> Option<&dyn $crate::Throwable> { + Some(&self.$field) + } + + fn parent_mut(&mut self) -> Option<&mut dyn $crate::Throwable> { + Some(&mut self.$field) + } + } + + impl From<$ty> for $crate::AnyThrowable { + fn from(exception: $ty) -> Self { + $crate::AnyThrowable::new(exception) + } + } + + impl From<$ty> for ::anyhow::Error { + fn from(exception: $ty) -> Self { + ::anyhow::Error::new($crate::AnyThrowable::new(exception)) + } + } + }; + // For an exception the port cannot let travel as a Rust error, because its state is not + // `Send + Sync`. It gets the accessors but no [`Throwable`], so asking for it in a `catch` + // does not compile, rather than silently never matching. + ($ty:ty, $field:tt, $class_name:expr, !Send) => { + $crate::impl_php_exception!(@accessors $ty, $field, $class_name); + }; + (@accessors $ty:ty, $field:tt, $class_name:expr) => { + impl $ty { + pub fn get_message(&self) -> &str { + self.$field.get_message() + } + + pub fn get_code(&self) -> i64 { + self.$field.get_code() + } + + pub fn set_code(&mut self, code: i64) { + self.$field.set_code(code); + } + + pub fn get_previous(&self) -> Option<&$crate::AnyThrowable> { + self.$field.get_previous() + } + } + + impl std::fmt::Display for $ty { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.$field, f) + } + } + + impl $crate::PhpClass for $ty { + fn php_class_name(&self) -> String { + $class_name.to_string() + } + } + }; } -impl std::fmt::Display for OutOfBoundsException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.message) - } +/// Defines a PHP built-in exception class as a struct carrying nothing but the instance of the +/// class it extends, or the [`ThrowableFields`] itself for a class that extends nothing. +macro_rules! define_php_exception { + ($ty:ident, ThrowableFields, $class_name:expr) => { + define_php_exception!(@shared $ty, ThrowableFields, $class_name); + + impl $ty { + pub fn with_code_and_previous( + message: String, + code: i64, + previous: Option<std::sync::Arc<AnyThrowable>>, + ) -> Self { + Self { + inner: ThrowableFields { + message, + code, + previous, + }, + } + } + } + }; + ($ty:ident, $parent:ty, $class_name:expr) => { + define_php_exception!(@shared $ty, $parent, $class_name); + + impl $ty { + pub fn with_code_and_previous( + message: String, + code: i64, + previous: Option<std::sync::Arc<AnyThrowable>>, + ) -> Self { + Self { + inner: <$parent>::with_code_and_previous(message, code, previous), + } + } + } + }; + (@shared $ty:ident, $parent:ty, $class_name:expr) => { + #[derive(Debug, Clone)] + pub struct $ty { + inner: $parent, + } + + impl $ty { + pub fn new(message: String) -> Self { + Self::with_code_and_previous(message, 0, None) + } + + pub fn with_code(message: String, code: i64) -> Self { + Self::with_code_and_previous(message, code, None) + } + } + + crate::impl_php_exception!($ty, inner, $class_name); + }; } -impl std::error::Error for OutOfBoundsException {} +define_php_exception!(Exception, ThrowableFields, r"Exception"); +define_php_exception!(Error, ThrowableFields, r"Error"); +define_php_exception!(TypeError, Error, r"TypeError"); +define_php_exception!(RuntimeException, Exception, r"RuntimeException"); +define_php_exception!( + UnexpectedValueException, + RuntimeException, + r"UnexpectedValueException" +); +define_php_exception!( + OutOfBoundsException, + RuntimeException, + r"OutOfBoundsException" +); +define_php_exception!(LogicException, Exception, r"LogicException"); +define_php_exception!( + InvalidArgumentException, + LogicException, + r"InvalidArgumentException" +); +define_php_exception!( + BadFunctionCallException, + LogicException, + r"BadFunctionCallException" +); +define_php_exception!( + BadMethodCallException, + BadFunctionCallException, + r"BadMethodCallException" +); #[derive(Debug)] pub struct ErrorException { - pub message: String, - pub code: i64, + inner: Exception, pub severity: i64, pub filename: String, pub lineno: i64, } -impl std::fmt::Display for ErrorException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.message) +impl ErrorException { + pub fn new( + message: String, + code: i64, + severity: i64, + filename: String, + lineno: i64, + previous: Option<std::sync::Arc<AnyThrowable>>, + ) -> Self { + Self { + inner: Exception::with_code_and_previous(message, code, previous), + severity, + filename, + lineno, + } } } -impl std::error::Error for ErrorException {} +crate::impl_php_exception!(ErrorException, inner, r"ErrorException"); /// Models PHP's `exit`/`die` language construct propagated as a recoverable error so the actual /// process termination happens at a single top-level site instead of deep in the call stack. @@ -148,39 +431,103 @@ impl std::fmt::Display for ExitException { impl std::error::Error for ExitException {} -pub fn php_exception_get_code(_error: &anyhow::Error) -> i32 { - // PHP's Throwable::getCode(). anyhow::Error carries the concrete exception type, so enumerate - // the flat standard exception structs and read their `code` field; everything else defaults to - // 0, matching PHP's default exception code. - if let Some(e) = _error.downcast_ref::<Exception>() { - return e.code as i32; +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Debug)] + struct Subclass { + inner: UnexpectedValueException, + detail: i64, } - if let Some(e) = _error.downcast_ref::<RuntimeException>() { - return e.code as i32; + + impl Subclass { + fn new(detail: i64) -> Self { + Self { + inner: UnexpectedValueException::new("boom".to_string()), + detail, + } + } } - if let Some(e) = _error.downcast_ref::<UnexpectedValueException>() { - return e.code as i32; + + crate::impl_php_exception!(Subclass, inner, r"Vendor\Subclass"); + + #[test] + fn catch_reaches_every_superclass() { + let error: anyhow::Error = Subclass::new(7).into(); + + assert_eq!(error.catch::<Subclass>().map(|e| e.detail), Some(7)); + assert!(error.catch::<UnexpectedValueException>().is_some()); + assert!(error.catch::<RuntimeException>().is_some()); + assert!(error.catch::<Exception>().is_some()); + assert!(error.catch::<ThrowableFields>().is_some()); } - if let Some(e) = _error.downcast_ref::<InvalidArgumentException>() { - return e.code as i32; + + #[test] + fn catch_reaches_no_sibling_or_subclass() { + let error: anyhow::Error = RuntimeException::new("boom".to_string()).into(); + + assert!(error.catch::<Subclass>().is_none()); + assert!(error.catch::<LogicException>().is_none()); + assert!(error.catch::<Error>().is_none()); } - if let Some(e) = _error.downcast_ref::<TypeError>() { - return e.code as i32; + + #[test] + fn an_error_is_not_an_exception() { + let error: anyhow::Error = TypeError::new("boom".to_string()).into(); + + assert!(error.catch::<Error>().is_some()); + assert!(error.catch::<ThrowableFields>().is_some()); + assert!(error.catch::<Exception>().is_none()); } - if let Some(e) = _error.downcast_ref::<LogicException>() { - return e.code as i32; + + #[test] + fn catch_reaches_nothing_in_a_plain_rust_error() { + let error = anyhow::anyhow!("boom"); + + assert!(error.catch::<ThrowableFields>().is_none()); } - if let Some(e) = _error.downcast_ref::<BadMethodCallException>() { - return e.code as i32; + + #[test] + fn is_class_reaches_no_superclass() { + let error: anyhow::Error = Subclass::new(7).into(); + + assert!(error.is_class::<Subclass>()); + assert!(error.is_instanceof::<UnexpectedValueException>()); + assert!(!error.is_class::<UnexpectedValueException>()); + assert!(!error.is_class::<ThrowableFields>()); } - if let Some(e) = _error.downcast_ref::<OutOfBoundsException>() { - return e.code as i32; + + #[test] + fn is_class_reaches_nothing_in_a_plain_rust_error() { + let error = anyhow::anyhow!("boom"); + + assert!(!error.is_class::<ThrowableFields>()); } - if let Some(e) = _error.downcast_ref::<ErrorException>() { - return e.code as i32; + + #[test] + fn catch_mut_writes_through_to_the_superclass_state() { + let mut error: anyhow::Error = Subclass::new(7).into(); + + error + .catch_mut::<UnexpectedValueException>() + .unwrap() + .set_code(42); + + assert_eq!(error.catch::<Subclass>().unwrap().get_code(), 42); } - if let Some(e) = _error.downcast_ref::<PharException>() { - return e.code as i32; + + #[test] + fn the_previous_exception_is_the_error_source() { + let previous = std::sync::Arc::new(AnyThrowable::new(RuntimeException::new( + "cause".to_string(), + ))); + let error: anyhow::Error = + Exception::with_code_and_previous("boom".to_string(), 0, Some(previous)).into(); + + let source = std::error::Error::source( + error.downcast_ref::<AnyThrowable>().unwrap() as &dyn std::error::Error + ); + assert_eq!(source.map(ToString::to_string), Some("cause".to_string())); } - 0 } diff --git a/crates/shirabe-php-shim/src/fs.rs b/crates/shirabe-php-shim/src/fs.rs index c3f131b3..00ed6de3 100644 --- a/crates/shirabe-php-shim/src/fs.rs +++ b/crates/shirabe-php-shim/src/fs.rs @@ -184,13 +184,10 @@ pub fn recursive_directory_iterator( ) -> Result<RecursiveDirectoryIterator, UnexpectedValueException> { let root = _path.as_ref().to_path_buf(); if !root.is_dir() { - return Err(UnexpectedValueException { - message: format!( - "RecursiveDirectoryIterator::__construct({}): Failed to open directory", - root.display() - ), - code: 0, - }); + return Err(UnexpectedValueException::new(format!( + "RecursiveDirectoryIterator::__construct({}): Failed to open directory", + root.display() + ))); } Ok(RecursiveDirectoryIterator { root, @@ -255,12 +252,11 @@ pub fn directory_iterator( path: impl AsRef<std::path::Path>, ) -> Result<Vec<DirectoryIteratorEntry>, UnexpectedValueException> { let base = path.as_ref(); - let rd = std::fs::read_dir(base).map_err(|_| UnexpectedValueException { - message: format!( + let rd = std::fs::read_dir(base).map_err(|_| { + UnexpectedValueException::new(format!( "DirectoryIterator::__construct({}): Failed to open directory", base.display() - ), - code: 0, + )) })?; // PHP's DirectoryIterator yields the "." and ".." entries before the real ones. let mut entries = vec![ diff --git a/crates/shirabe-php-shim/src/phar.rs b/crates/shirabe-php-shim/src/phar.rs index 0387a0de..a0299a84 100644 --- a/crates/shirabe-php-shim/src/phar.rs +++ b/crates/shirabe-php-shim/src/phar.rs @@ -23,14 +23,12 @@ struct PharEntry { } fn corruption_error(path: &std::path::Path, detail: &str) -> anyhow::Error { - anyhow::anyhow!(UnexpectedValueException { - message: format!( - "internal corruption of phar \"{}\" ({})", - path.display(), - detail - ), - code: 0, - }) + UnexpectedValueException::new(format!( + "internal corruption of phar \"{}\" ({})", + path.display(), + detail + )) + .into() } /// Reads a tar- or zip-based archive (optionally gzip/bzip2 compressed as a whole) @@ -152,14 +150,12 @@ fn extract_entries( overwrite: bool, ) -> anyhow::Result<()> { let extract_error = |detail: String| { - anyhow::anyhow!(PharException { - message: format!( - "Extracting from phar \"{}\" failed: {}", - archive_path.display(), - detail - ), - code: 0, - }) + PharException::new(format!( + "Extracting from phar \"{}\" failed: {}", + archive_path.display(), + detail + )) + .into() }; std::fs::create_dir_all(directory).map_err(|e| extract_error(e.to_string()))?; @@ -441,17 +437,18 @@ impl Phar { #[derive(Debug)] pub struct PharException { - pub message: String, - pub code: i64, + inner: crate::Exception, } -impl std::fmt::Display for PharException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.message) +impl PharException { + pub fn new(message: String) -> Self { + Self { + inner: crate::Exception::new(message), + } } } -impl std::error::Error for PharException {} +crate::impl_php_exception!(PharException, inner, r"PharException"); #[derive(Debug)] pub struct PharFileInfo { @@ -514,13 +511,10 @@ impl PharData { None => false, }; if !parent_exists { - return Err(anyhow::anyhow!(UnexpectedValueException { - message: format!( - "Cannot create phar '{}', file extension (or combination) not recognised or the directory does not exist", - path.display() - ), - code: 0, - })); + return Err(UnexpectedValueException::new(format!( + "Cannot create phar '{}', file extension (or combination) not recognised or the directory does not exist", + path.display() + )).into()); } let format = format.unwrap_or(if path.to_string_lossy().ends_with(".zip") { Phar::ZIP @@ -647,15 +641,13 @@ impl PharData { for file in iter { let localname = file .strip_prefix(base_directory) - .map_err(|_| { - anyhow::anyhow!(UnexpectedValueException { - message: format!( - "Iterator returned a path \"{}\" that is not in the base directory \"{}\"", - file.display(), - base_directory.display() - ), - code: 0, - }) + .map_err(|_| -> anyhow::Error { + UnexpectedValueException::new(format!( + "Iterator returned a path \"{}\" that is not in the base directory \"{}\"", + file.display(), + base_directory.display() + )) + .into() })? .to_string_lossy() .into_owned(); @@ -679,15 +671,13 @@ impl PharData { "PharData::compress: only tar-based archives can be compressed as a whole" ); let tar_bytes = self.build_tar_bytes()?; - let write_error = |e: std::io::Error| { - anyhow::anyhow!(PharException { - message: format!( - "Unable to compress phar archive \"{}\": {}", - self.path.display(), - e - ), - code: 0, - }) + let write_error = |e: std::io::Error| -> anyhow::Error { + PharException::new(format!( + "Unable to compress phar archive \"{}\": {}", + self.path.display(), + e + )) + .into() }; let (target, compressed) = match algo { Phar::GZ => { @@ -725,27 +715,23 @@ impl PharData { } let bytes = self.build_tar_bytes()?; std::fs::write(&self.path, bytes).map_err(|e| { - anyhow::anyhow!(PharException { - message: format!( - "Unable to write phar archive \"{}\": {}", - self.path.display(), - e - ), - code: 0, - }) + PharException::new(format!( + "Unable to write phar archive \"{}\": {}", + self.path.display(), + e + )) + .into() }) } fn build_tar_bytes(&self) -> anyhow::Result<Vec<u8>> { let write_error = |e: std::io::Error| { - anyhow::anyhow!(PharException { - message: format!( - "Unable to write phar archive \"{}\": {}", - self.path.display(), - e - ), - code: 0, - }) + PharException::new(format!( + "Unable to write phar archive \"{}\": {}", + self.path.display(), + e + )) + .into() }; let mut builder = tar::Builder::new(Vec::new()); for entry in self.entries.borrow().iter() { @@ -802,15 +788,13 @@ impl PharData { } fn write_zip(&self) -> anyhow::Result<()> { - let write_error = |e: String| { - anyhow::anyhow!(PharException { - message: format!( - "Unable to write phar archive \"{}\": {}", - self.path.display(), - e - ), - code: 0, - }) + let write_error = |e: String| -> anyhow::Error { + PharException::new(format!( + "Unable to write phar archive \"{}\": {}", + self.path.display(), + e + )) + .into() }; let file = std::fs::File::create(&self.path).map_err(|e| write_error(e.to_string()))?; let mut writer = zip::ZipWriter::new(file); @@ -860,6 +844,7 @@ impl PharData { #[cfg(test)] mod tests { use super::*; + use crate::Catch as _; fn write_file(dir: &std::path::Path, name: &str, content: &[u8]) -> std::path::PathBuf { let path = dir.join(name); @@ -934,9 +919,9 @@ mod tests { let error = PharData::new("/nonexistent-dir/foo.tar").unwrap_err(); assert!( error - .downcast_ref::<UnexpectedValueException>() + .catch::<UnexpectedValueException>() .unwrap() - .message + .get_message() .starts_with("Cannot create phar") ); } @@ -1030,9 +1015,9 @@ mod tests { let error = Phar::new(&phar_path).unwrap_err(); assert!( error - .downcast_ref::<UnexpectedValueException>() + .catch::<UnexpectedValueException>() .unwrap() - .message + .get_message() .contains("broken signature") ); } diff --git a/crates/shirabe-php-shim/src/var.rs b/crates/shirabe-php-shim/src/var.rs index dfbd2f03..8697097d 100644 --- a/crates/shirabe-php-shim/src/var.rs +++ b/crates/shirabe-php-shim/src/var.rs @@ -222,14 +222,6 @@ pub fn get_class(_object: &PhpMixed) -> String { todo!() } -// Overload accepting an `anyhow::Error` (PHP's `get_class($e)` is commonly used on exceptions). -pub fn get_class_err(_e: &anyhow::Error) -> String { - // TODO(phase-c): PHP returns the exception's class name. anyhow::Error carries the concrete - // exception type, but mapping each ported exception struct to its PHP class name is not yet - // wired up (cf. php_exception_get_code which downcasts case by case). - todo!() -} - pub fn get_debug_type(value: &PhpMixed) -> String { match value { PhpMixed::Null => "null".to_string(), diff --git a/crates/shirabe-php-shim/src/zip.rs b/crates/shirabe-php-shim/src/zip.rs index 6a088419..7284b029 100644 --- a/crates/shirabe-php-shim/src/zip.rs +++ b/crates/shirabe-php-shim/src/zip.rs @@ -146,13 +146,10 @@ impl ZipArchive { pub fn extract_to(&self, path: impl AsRef<std::path::Path>) -> Result<bool, ErrorException> { if let Some(mock) = &self.mock { - return mock.extract_to.clone().map_err(|message| ErrorException { - message, - code: 0, - severity: 1, - filename: String::new(), - lineno: 0, - }); + return mock + .extract_to + .clone() + .map_err(|message| ErrorException::new(message, 0, 1, String::new(), 0, None)); } let mut state = self.state.borrow_mut(); let ZipState::Reader(archive) = &mut *state else { diff --git a/crates/shirabe/src/advisory/audit_config.rs b/crates/shirabe/src/advisory/audit_config.rs index 71730317..fc1cbedb 100644 --- a/crates/shirabe/src/advisory/audit_config.rs +++ b/crates/shirabe/src/advisory/audit_config.rs @@ -103,13 +103,10 @@ impl AuditConfig { .map(|s| s.to_string()); if !["audit", "block", "all"].contains(&apply.as_str()) { - return Err(InvalidArgumentException { - message: format!( - "Invalid 'apply' value for '{}': {}. Expected 'audit', 'block', or 'all'.", - key, apply - ), - code: 0, - }.into()); + return Err(InvalidArgumentException::new(format!( + "Invalid 'apply' value for '{}': {}. Expected 'audit', 'block', or 'all'.", + key, apply + )).into()); } (key.clone(), apply, reason) diff --git a/crates/shirabe/src/advisory/auditor.rs b/crates/shirabe/src/advisory/auditor.rs index d20c9453..d661cf90 100644 --- a/crates/shirabe/src/advisory/auditor.rs +++ b/crates/shirabe/src/advisory/auditor.rs @@ -422,13 +422,10 @@ impl Auditor { .map(|buffer_io| &buffer_io.inner) }); if io_as_console.is_none() { - return Err(InvalidArgumentException { - message: format!( - "Cannot use table format with {}", - get_class(&PhpMixed::Null) - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Cannot use table format with {}", + get_class(&PhpMixed::Null) + )) .into()); } self.output_advisories_table(io_as_console.unwrap(), advisories)?; @@ -441,11 +438,9 @@ impl Auditor { Ok(()) } Self::FORMAT_SUMMARY => Ok(()), - _ => Err(InvalidArgumentException { - message: format!("Invalid format \"{}\".", format), - code: 0, + _ => { + Err(InvalidArgumentException::new(format!("Invalid format \"{}\".", format)).into()) } - .into()), } } @@ -597,13 +592,10 @@ impl Auditor { .map(|buffer_io| &buffer_io.inner) }); if io_as_console.is_none() { - return Err(InvalidArgumentException { - message: format!( - "Cannot use table format with {}", - get_class(&PhpMixed::Null) - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Cannot use table format with {}", + get_class(&PhpMixed::Null) + )) .into()); } diff --git a/crates/shirabe/src/autoload/autoload_generator.rs b/crates/shirabe/src/autoload/autoload_generator.rs index aacbfc0f..4eae319d 100644 --- a/crates/shirabe/src/autoload/autoload_generator.rs +++ b/crates/shirabe/src/autoload/autoload_generator.rs @@ -815,19 +815,13 @@ return array( { let name = package.get_name(); let _ = package.get_target_dir(); - return Err(InvalidArgumentException { - message: format!("PSR-4 autoloading is incompatible with the target-dir property, remove the target-dir in package '{}'.", name), - code: 0, - } + return Err(InvalidArgumentException::new(format!("PSR-4 autoloading is incompatible with the target-dir property, remove the target-dir in package '{}'.", name)) .into()); } if let Some(psr4) = autoload.get("psr-4").and_then(|v| v.as_array()) { for (namespace, _dirs) in psr4 { if !namespace.is_empty() && !namespace.ends_with('\\') { - return Err(InvalidArgumentException { - message: format!("psr-4 namespaces must end with a namespace separator, '{}' does not, use '{}\\'.", namespace, namespace), - code: 0, - } + return Err(InvalidArgumentException::new(format!("psr-4 namespaces must end with a namespace separator, '{}' does not, use '{}\\'.", namespace, namespace)) .into()); } } diff --git a/crates/shirabe/src/autoload/class_loader.rs b/crates/shirabe/src/autoload/class_loader.rs index 89758269..47b6ee4c 100644 --- a/crates/shirabe/src/autoload/class_loader.rs +++ b/crates/shirabe/src/autoload/class_loader.rs @@ -175,11 +175,9 @@ impl ClassLoader { // Register directories for a new namespace. let length = strlen(prefix); if "\\" != &prefix[(length as usize - 1)..(length as usize)] { - return Err(InvalidArgumentException { - message: "A non-empty PSR-4 prefix must end with a namespace separator." - .to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "A non-empty PSR-4 prefix must end with a namespace separator.".to_string(), + ) .into()); } let first = prefix.chars().next().unwrap_or('\0').to_string(); @@ -226,11 +224,9 @@ impl ClassLoader { } else { let length = strlen(prefix); if "\\" != &prefix[(length as usize - 1)..(length as usize)] { - return Err(InvalidArgumentException { - message: "A non-empty PSR-4 prefix must end with a namespace separator." - .to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "A non-empty PSR-4 prefix must end with a namespace separator.".to_string(), + ) .into()); } let first = prefix.chars().next().unwrap_or('\0').to_string(); diff --git a/crates/shirabe/src/cache.rs b/crates/shirabe/src/cache.rs index 354e559e..a9f56eac 100644 --- a/crates/shirabe/src/cache.rs +++ b/crates/shirabe/src/cache.rs @@ -180,7 +180,7 @@ impl Cache { self.io.write_error3( &format!( "<warning>Failed to write into cache: {}</warning>", - e.message + e.get_message() ), true, crate::io::DEBUG, @@ -190,7 +190,7 @@ impl Cache { php_regex!( r"{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}" ), - &e.message, + e.get_message(), Some(&mut m), ) { // Remove partial file. diff --git a/crates/shirabe/src/command/archive_command.rs b/crates/shirabe/src/command/archive_command.rs index e749d13f..1c428c58 100644 --- a/crates/shirabe/src/command/archive_command.rs +++ b/crates/shirabe/src/command/archive_command.rs @@ -303,13 +303,10 @@ impl ArchiveCommand { }; let Some(complete) = package.as_complete() else { - return Err(LogicException { - message: format!( - "Expected a CompletePackageInterface instance but found {}", - get_debug_type(&shirabe_php_shim::PhpMixed::Null) - ), - code: 0, - } + return Err(LogicException::new(format!( + "Expected a CompletePackageInterface instance but found {}", + get_debug_type(&shirabe_php_shim::PhpMixed::Null) + )) .into()); }; diff --git a/crates/shirabe/src/command/audit_command.rs b/crates/shirabe/src/command/audit_command.rs index 0eee0036..268984d6 100644 --- a/crates/shirabe/src/command/audit_command.rs +++ b/crates/shirabe/src/command/audit_command.rs @@ -60,10 +60,7 @@ impl AuditCommand { let locker = composer.get_locker().clone(); let mut locker = locker.borrow_mut(); if !locker.is_locked() { - return Err(UnexpectedValueException { - message: "Valid composer.json and composer.lock files are required to run this command with --locked".to_string(), - code: 0, - }.into()); + return Err(UnexpectedValueException::new("Valid composer.json and composer.lock files are required to run this command with --locked".to_string()).into()); } let locked_repo = locker.get_locked_repository( !input @@ -228,19 +225,16 @@ impl Command for AuditCommand { .collect::<Vec<_>>(), ) { - return Err(InvalidArgumentException { - message: format!( - "--abandoned must be one of {}.", - implode( - ", ", - &Auditor::ABANDONEDS - .iter() - .map(|s| s.to_string()) - .collect::<Vec<_>>() - ) - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "--abandoned must be one of {}.", + implode( + ", ", + &Auditor::ABANDONEDS + .iter() + .map(|s| s.to_string()) + .collect::<Vec<_>>() + ) + )) .into()); } diff --git a/crates/shirabe/src/command/base_command.rs b/crates/shirabe/src/command/base_command.rs index 7a66862e..a1de9783 100644 --- a/crates/shirabe/src/command/base_command.rs +++ b/crates/shirabe/src/command/base_command.rs @@ -294,10 +294,7 @@ impl BaseCommand for BaseCommandData { if self.composer.borrow().is_none() { let application = self.get_application(); let Some(application) = application else { - return Err(RuntimeException { - message: "Could not create a Composer\\Composer instance, you must inject one if this command is not used with a Composer\\Console\\Application instance".to_string(), - code: 0, - } + return Err(RuntimeException::new("Could not create a Composer\\Composer instance, you must inject one if this command is not used with a Composer\\Console\\Application instance".to_string()) .into()); }; let composer = { @@ -455,11 +452,9 @@ impl BaseCommand for BaseCommandData { .as_bool() .unwrap_or(false) { - return Err(InvalidArgumentException { - message: "--prefer-source can not be used together with --prefer-install" - .to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "--prefer-source can not be used together with --prefer-install".to_string(), + ) .into()); } if input @@ -468,11 +463,9 @@ impl BaseCommand for BaseCommandData { .as_bool() .unwrap_or(false) { - return Err(InvalidArgumentException { - message: "--prefer-dist can not be used together with --prefer-install" - .to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "--prefer-dist can not be used together with --prefer-install".to_string(), + ) .into()); } let prefer_install = input.borrow().get_option("prefer-install")?; @@ -492,13 +485,10 @@ impl BaseCommand for BaseCommandData { prefer_source = false; } other => { - return Err(UnexpectedValueException { - message: format!( - "--prefer-install accepts one of \"dist\", \"source\" or \"auto\", got {}", - other - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "--prefer-install accepts one of \"dist\", \"source\" or \"auto\", got {}", + other + )) .into()); } } @@ -551,12 +541,8 @@ impl BaseCommand for BaseCommandData { if !input.borrow().has_option("ignore-platform-reqs") || !input.borrow().has_option("ignore-platform-req") { - return Err(LogicException { - message: - "Calling getPlatformRequirementFilter from a command which does not define the --ignore-platform-req[s] flags is not permitted." - .to_string(), - code: 0, - } + return Err(LogicException::new("Calling getPlatformRequirementFilter from a command which does not define the --ignore-platform-req[s] flags is not permitted." + .to_string()) .into()); } @@ -585,14 +571,11 @@ impl BaseCommand for BaseCommandData { let requirements = self.normalize_requirements(requirements)?; for requirement in requirements { if !requirement.contains_key("version") { - return Err(UnexpectedValueException { - message: format!( - "Option {} is missing a version constraint, use e.g. {}:^1.0", - requirement.get("name").map(|s| s.as_str()).unwrap_or(""), - requirement.get("name").map(|s| s.as_str()).unwrap_or(""), - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Option {} is missing a version constraint, use e.g. {}:^1.0", + requirement.get("name").map(|s| s.as_str()).unwrap_or(""), + requirement.get("name").map(|s| s.as_str()).unwrap_or(""), + )) .into()); } requires.insert( @@ -648,13 +631,10 @@ impl BaseCommand for BaseCommandData { opt_name: &str, ) -> anyhow::Result<String> { if !input.borrow().has_option(opt_name) { - return Err(LogicException { - message: format!( - "This should not be called on a Command which has no {} option defined.", - opt_name - ), - code: 0, - } + return Err(LogicException::new(format!( + "This should not be called on a Command which has no {} option defined.", + opt_name + )) .into()); } @@ -664,14 +644,11 @@ impl BaseCommand for BaseCommandData { .map(|s| PhpMixed::String(s.to_string())) .collect(); if !in_array_strict(val.clone(), &formats) { - return Err(InvalidArgumentException { - message: format!( - "--{} must be one of {}.", - opt_name, - Auditor::FORMATS.join(", ") - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "--{} must be one of {}.", + opt_name, + Auditor::FORMATS.join(", ") + )) .into()); } diff --git a/crates/shirabe/src/command/base_dependency_command.rs b/crates/shirabe/src/command/base_dependency_command.rs index 46cc654f..e46cac8c 100644 --- a/crates/shirabe/src/command/base_dependency_command.rs +++ b/crates/shirabe/src/command/base_dependency_command.rs @@ -63,12 +63,11 @@ pub trait BaseDependencyCommand: BaseCommand { let mut locker = locker.borrow_mut(); if !locker.is_locked() { - return Err(anyhow::anyhow!(UnexpectedValueException { - message: - "A valid composer.lock file is required to run this command with --locked" - .to_string(), - code: 0, - })); + return Err(UnexpectedValueException::new( + "A valid composer.lock file is required to run this command with --locked" + .to_string(), + ) + .into()); } repos.push(locker.get_locked_repository(true)?.into()); @@ -134,10 +133,11 @@ pub trait BaseDependencyCommand: BaseCommand { let packages = installed_repo.find_packages_with_replacers_and_providers(&needle, None)?; if packages.is_empty() { - return Err(anyhow::anyhow!(InvalidArgumentException { - message: format!("Could not find package \"{}\" in your project", needle), - code: 0, - })); + return Err(InvalidArgumentException::new(format!( + "Could not find package \"{}\" in your project", + needle + )) + .into()); } let matched_package = installed_repo.find_package( diff --git a/crates/shirabe/src/command/bump_command.rs b/crates/shirabe/src/command/bump_command.rs index a4518b0b..51a779ef 100644 --- a/crates/shirabe/src/command/bump_command.rs +++ b/crates/shirabe/src/command/bump_command.rs @@ -305,10 +305,10 @@ impl BumpCommand { let contents = match file_get_contents(json.get_path()) { Some(c) => c, None => { - return Err(shirabe_php_shim::RuntimeException { - message: format!("Unable to read {} contents.", json.get_path()), - code: 0, - } + return Err(shirabe_php_shim::RuntimeException::new(format!( + "Unable to read {} contents.", + json.get_path() + )) .into()); } }; @@ -325,10 +325,10 @@ impl BumpCommand { match file_put_contents(json.get_path(), manipulator.get_contents().as_bytes()) { Some(_) => Ok(true), - None => Err(shirabe_php_shim::RuntimeException { - message: format!("Unable to write new {} contents.", json.get_path()), - code: 0, - } + None => Err(shirabe_php_shim::RuntimeException::new(format!( + "Unable to write new {} contents.", + json.get_path() + )) .into()), } } diff --git a/crates/shirabe/src/command/config_command.rs b/crates/shirabe/src/command/config_command.rs index 2727f36b..3b976ba2 100644 --- a/crates/shirabe/src/command/config_command.rs +++ b/crates/shirabe/src/command/config_command.rs @@ -86,11 +86,10 @@ impl ConfigCommand { ) -> anyhow::Result<()> { let (validator, normalizer) = callbacks; if 1 != values.len() { - return Err(RuntimeException { - message: "You can only pass one value. Example: shirabe config process-timeout 300" + return Err(RuntimeException::new( + "You can only pass one value. Example: shirabe config process-timeout 300" .to_string(), - code: 0, - } + ) .into()); } @@ -101,10 +100,11 @@ impl ConfigCommand { } else { String::new() }; - return Err(RuntimeException { - message: format!("\"{}\" is an invalid value{}", values[0].clone(), suffix), - code: 0, - } + return Err(RuntimeException::new(format!( + "\"{}\" is an invalid value{}", + values[0].clone(), + suffix + )) .into()); } @@ -160,14 +160,11 @@ impl ConfigCommand { } else { String::new() }; - return Err(RuntimeException { - message: format!( - "{} is an invalid value{}", - PhpMixed::from(json_encode(&values_mixed).ok()), - suffix - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "{} is an invalid value{}", + PhpMixed::from(json_encode(&values_mixed).ok()), + suffix + )) .into()); } @@ -679,10 +676,9 @@ impl Command for ConfigCommand { .unwrap_or_default(); if !setting_values.is_empty() && input.borrow().get_option("unset")?.as_bool() == Some(true) { - return Err(RuntimeException { - message: "You can not combine a setting value with --unset".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "You can not combine a setting value with --unset".to_string(), + ) .into()); } @@ -731,10 +727,10 @@ impl Command for ConfigCommand { { Some(v) => v.clone(), None => { - return Err(InvalidArgumentException { - message: format!("There is no {} repository defined", repo_key), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "There is no {} repository defined", + repo_key + )) .into()); } }; @@ -766,11 +762,9 @@ impl Command for ConfigCommand { } if !r#match { - return Err(RuntimeException { - message: format!("{} is not defined.", setting_key), - code: 0, - } - .into()); + return Err( + RuntimeException::new(format!("{} is not defined.", setting_key)).into(), + ); } value = cursor; @@ -847,11 +841,9 @@ impl Command for ConfigCommand { value = v.clone(); source = "defaults".to_string(); } else { - return Err(RuntimeException { - message: format!("{} is not defined", setting_key), - code: 0, - } - .into()); + return Err( + RuntimeException::new(format!("{} is not defined", setting_key)).into(), + ); } let value_str = if is_array(&value) || is_object(&value) || is_bool(&value) { @@ -961,13 +953,10 @@ impl Command for ConfigCommand { .as_bool() .unwrap_or(false) { - return Err(RuntimeException { - message: format!( - "Invalid value for {}. Should be one of: auto, source, or dist", - setting_key - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Invalid value for {}. Should be one of: auto, source, or dist", + setting_key + )) .into()); } @@ -998,10 +987,10 @@ impl Command for ConfigCommand { } if !boolean_validator(&PhpMixed::String(values[0].clone())) { - return Err(RuntimeException { - message: format!("\"{}\" is an invalid value", values[0].clone()), - code: 0, - } + return Err(RuntimeException::new(format!( + "\"{}\" is an invalid value", + values[0].clone() + )) .into()); } @@ -1025,10 +1014,7 @@ impl Command for ConfigCommand { || multi_props.contains_key(&setting_key) || strpos(&setting_key, "extra.") == Some(0)) { - return Err(InvalidArgumentException { - message: format!("The {} property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json", setting_key), - code: 0, - } + return Err(InvalidArgumentException::new(format!("The {} property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json", setting_key)) .into()); } if input.borrow().get_option("unset")?.as_bool() == Some(true) @@ -1122,10 +1108,7 @@ impl Command for ConfigCommand { } } - return Err(RuntimeException { - message: "You must pass the type and a url. Example: shirabe config repositories.foo vcs https://bar.com".to_string(), - code: 0, - } + return Err(RuntimeException::new("You must pass the type and a url. Example: shirabe config repositories.foo vcs https://bar.com".to_string()) .into()); } @@ -1308,10 +1291,10 @@ impl Command for ConfigCommand { if input.borrow().get_option("json")?.as_bool() == Some(true) { value = JsonFile::parse_json(Some(&values[0]), Some("composer.json"))?; if !is_array(&value) { - return Err(RuntimeException { - message: format!("Expected an array or object for {}", setting_key), - code: 0, - } + return Err(RuntimeException::new(format!( + "Expected an array or object for {}", + setting_key + )) .into()); } } @@ -1349,10 +1332,10 @@ impl Command for ConfigCommand { } value = PhpMixed::Array(merged); } else { - return Err(RuntimeException { - message: format!("Cannot merge array and object for {}", setting_key), - code: 0, - } + return Err(RuntimeException::new(format!( + "Cannot merge array and object for {}", + setting_key + )) .into()); } } @@ -1394,13 +1377,10 @@ impl Command for ConfigCommand { let key = format!("{}.{}", matches[1], matches[2]); if matches[1] == "bitbucket-oauth" { if 2 != values.len() { - return Err(RuntimeException { - message: format!( - "Expected two arguments (consumer-key, consumer-secret), got {}", - values.len() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Expected two arguments (consumer-key, consumer-secret), got {}", + values.len() + )) .into()); } self.config_source @@ -1441,10 +1421,9 @@ impl Command for ConfigCommand { "github-oauth" | "gitlab-oauth" | "gitlab-token" | "bearer" ) { if 1 != values.len() { - return Err(RuntimeException { - message: "Too many arguments, expected only one token".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "Too many arguments, expected only one token".to_string(), + ) .into()); } self.config_source @@ -1459,13 +1438,10 @@ impl Command for ConfigCommand { .add_config_setting(&key, PhpMixed::String(values[0].clone())); } else if matches[1] == "http-basic" { if 2 != values.len() { - return Err(RuntimeException { - message: format!( - "Expected two arguments (username, password), got {}", - values.len() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Expected two arguments (username, password), got {}", + values.len() + )) .into()); } self.config_source @@ -1483,10 +1459,9 @@ impl Command for ConfigCommand { .add_config_setting(&key, PhpMixed::Array(obj)); } else if matches[1] == "custom-headers" { if values.is_empty() { - return Err(RuntimeException { - message: "Expected at least one argument (header), got none".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "Expected at least one argument (header), got none".to_string(), + ) .into()); } @@ -1494,12 +1469,10 @@ impl Command for ConfigCommand { let mut formatted_headers: Vec<PhpMixed> = vec![]; for header in &values { if !is_string(&PhpMixed::String(header.clone())) { - return Err(RuntimeException { - message: - "Headers must be strings in \"Header-Name: Header-Value\" format" - .to_string(), - code: 0, - } + return Err(RuntimeException::new( + "Headers must be strings in \"Header-Name: Header-Value\" format" + .to_string(), + ) .into()); } @@ -1510,13 +1483,10 @@ impl Command for ConfigCommand { header, Some(&mut header_parts), ) { - return Err(RuntimeException { - message: format!( - "Header \"{}\" is not in \"Header-Name: Header-Value\" format", - header - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Header \"{}\" is not in \"Header-Name: Header-Value\" format", + header + )) .into()); } @@ -1535,13 +1505,10 @@ impl Command for ConfigCommand { .add_config_setting(&key, PhpMixed::List(formatted_headers)); } else if matches[1] == "forgejo-token" { if 2 != values.len() { - return Err(RuntimeException { - message: format!( - "Expected two arguments (username, access token), got {}", - values.len() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Expected two arguments (username, access token), got {}", + values.len() + )) .into()); } self.config_source @@ -1604,13 +1571,10 @@ impl Command for ConfigCommand { return Ok(0); } - Err(InvalidArgumentException { - message: format!( - "Setting {} does not exist or is not supported by this command", - setting_key - ), - code: 0, - } + Err(InvalidArgumentException::new(format!( + "Setting {} does not exist or is not supported by this command", + setting_key + )) .into()) } diff --git a/crates/shirabe/src/command/create_project_command.rs b/crates/shirabe/src/command/create_project_command.rs index aa1b79a8..267c866e 100644 --- a/crates/shirabe/src/command/create_project_command.rs +++ b/crates/shirabe/src/command/create_project_command.rs @@ -42,6 +42,7 @@ use shirabe_external_packages::symfony::console::command::command::Command; use shirabe_external_packages::symfony::console::input::InputInterface; use shirabe_external_packages::symfony::console::output::OutputInterface; use shirabe_external_packages::symfony::finder::Finder; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ DIRECTORY_SEPARATOR, InvalidArgumentException, PhpMixed, RuntimeException, UnexpectedValueException, array_pop, chdir, explode_with_limit, file_exists, getcwd, @@ -311,7 +312,7 @@ impl CreateProjectCommand { } } Err(e) => { - if e.downcast_ref::<PluginBlockedException>().is_some() { + if e.is_instanceof::<PluginBlockedException>() { io.write_error("<error>Hint: To allow running the config command recommended below before dependencies are installed, run create-project with --no-install.</error>"); io.write_error(&format!( "<error>You can then cd into {}, configure allow-plugins, and finally run a composer install to complete the process.</error>", @@ -365,11 +366,7 @@ impl CreateProjectCommand { for dir in &dirs { if !fs.remove_directory(dir)? { had_error = Some( - RuntimeException { - message: format!("Could not remove {}", dir.display()), - code: 0, - } - .into(), + RuntimeException::new(format!("Could not remove {}", dir.display())).into(), ); break; } @@ -485,10 +482,9 @@ impl CreateProjectCommand { ); } if directory.is_empty() { - return Err(UnexpectedValueException { - message: "Got an empty target directory, something went wrong".to_string(), - code: 0, - } + return Err(UnexpectedValueException::new( + "Got an empty target directory, something went wrong".to_string(), + ) .into()); } @@ -513,20 +509,17 @@ impl CreateProjectCommand { if file_exists(&directory) { if !is_dir(&directory) { - return Err(InvalidArgumentException { - message: format!( - "Cannot create project directory at \"{}\", it exists as a file.", - directory - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Cannot create project directory at \"{}\", it exists as a file.", + directory + )) .into()); } if !fs.borrow().is_dir_empty(&directory) { - return Err(InvalidArgumentException { - message: format!("Project directory \"{}\" is not empty.", directory), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Project directory \"{}\" is not empty.", + directory + )) .into()); } } @@ -575,20 +568,17 @@ impl CreateProjectCommand { .unwrap_or_default(); if !STABILITIES.contains_key(stability.as_str()) { - return Err(InvalidArgumentException { - message: format!( - "Invalid stability provided ({}), must be one of: {}", - stability, - implode( - ", ", - &STABILITIES - .keys() - .map(|k| k.to_string()) - .collect::<Vec<_>>() - ) - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Invalid stability provided ({}), must be one of: {}", + stability, + implode( + ", ", + &STABILITIES + .keys() + .map(|k| k.to_string()) + .collect::<Vec<_>>() + ) + )) .into()); } @@ -730,21 +720,14 @@ impl CreateProjectCommand { )? .is_some() { - return Err(InvalidArgumentException { - message: format!( - "{} in a version installable using your PHP version, PHP extensions and Composer version.", - error_message - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "{} in a version installable using your PHP version, PHP extensions and Composer version.", + error_message + )) .into()); } - return Err(InvalidArgumentException { - message: format!("{}.", error_message), - code: 0, - } - .into()); + return Err(InvalidArgumentException::new(format!("{}.", error_message)).into()); } let mut package = package.unwrap(); @@ -936,10 +919,9 @@ impl Command for CreateProjectCommand { { let package = input.borrow().get_argument("package")?; if package.is_null() { - return Err(RuntimeException { - message: "Not enough arguments (missing: \"package\").".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "Not enough arguments (missing: \"package\").".to_string(), + ) .into()); } let mut parts = diff --git a/crates/shirabe/src/command/diagnose_command.rs b/crates/shirabe/src/command/diagnose_command.rs index 72b58dea..857e517d 100644 --- a/crates/shirabe/src/command/diagnose_command.rs +++ b/crates/shirabe/src/command/diagnose_command.rs @@ -38,11 +38,12 @@ use shirabe_external_packages::symfony::console::command::command::Command; use shirabe_external_packages::symfony::console::input::InputInterface; use shirabe_external_packages::symfony::console::output::OutputInterface; use shirabe_external_packages::symfony::process::ExecutableFinder; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ - CmpOp, InvalidArgumentException, PHP_EOL, PhpMixed, disk_free_space, file_exists, - filter_var_boolean, get_class_err, hash, impl_php_class, implode, is_array, is_string, - php_regex, rtrim, str_contains, str_replace, str_starts_with, strpos, strstr, strstr3, - strtolower, trim, version_compare, + AnyThrowable, CmpOp, InvalidArgumentException, PHP_EOL, PhpClass as _, PhpMixed, + disk_free_space, file_exists, filter_var_boolean, hash, impl_php_class, implode, is_array, + is_string, php_regex, rtrim, str_contains, str_replace, str_starts_with, strpos, strstr, + strstr3, strtolower, trim, version_compare, }; #[derive(Debug)] @@ -106,7 +107,7 @@ impl DiagnoseCommand { match json.validate_schema(JsonFile::LOCK_SCHEMA, None) { Ok(_) => {} Err(e) => { - if let Some(jve) = e.downcast_ref::<JsonValidationException>() { + if let Some(jve) = e.catch::<JsonValidationException>() { let mut output = String::new(); for error in jve.get_errors() { output.push_str(&format!("<error>{}</error>{}", error, PHP_EOL)); @@ -191,7 +192,7 @@ impl DiagnoseCommand { ) { Ok(_) => {} Err(e) => { - if let Some(te) = e.downcast_ref::<TransportException>() { + if let Some(te) = e.catch::<TransportException>() { let hints = HttpDownloader::get_exception_hints(&e).unwrap_or_default(); if !hints.is_empty() { for hint in hints { @@ -202,7 +203,7 @@ impl DiagnoseCommand { result_list.push(PhpMixed::String(format!( "<error>[{}] {}</error>", std::any::type_name_of_val(te), - te.message + te.get_message() ))); } else { return Err(e); @@ -249,7 +250,7 @@ impl DiagnoseCommand { { Ok(_) => {} Err(e) => { - if let Some(te) = e.downcast_ref::<TransportException>() { + if let Some(te) = e.catch::<TransportException>() { let hints = HttpDownloader::get_exception_hints(&e).unwrap_or_default(); if !hints.is_empty() { for hint in hints { @@ -260,7 +261,7 @@ impl DiagnoseCommand { result_list.push(PhpMixed::String(format!( "<error>[{}] {}</error>", std::any::type_name_of_val(te), - te.message + te.get_message() ))); } else { return Err(e); @@ -394,7 +395,7 @@ impl DiagnoseCommand { ))) } Err(e) => { - if let Some(te) = e.downcast_ref::<TransportException>() + if let Some(te) = e.catch::<TransportException>() && te.get_code() == 401 { return Ok(PhpMixed::String(format!( @@ -404,7 +405,9 @@ impl DiagnoseCommand { } Ok(PhpMixed::String(format!( "<error>[{}] {}</error>", - get_class_err(&e), + AnyThrowable::of(e.as_ref()) + .expect("PHP reaches this only with a caught \\Throwable") + .php_class_name(), e ))) } @@ -540,13 +543,16 @@ impl DiagnoseCommand { Ok(Err(e)) => { return Ok(PhpMixed::String(format!( "<error>[{}] {}</error>", - "UnexpectedValueException", e.message + "UnexpectedValueException", + e.get_message() ))); } Err(e) => { return Ok(PhpMixed::String(format!( "<error>[{}] {}</error>", - get_class_err(&e), + AnyThrowable::of(e.as_ref()) + .expect("PHP reaches this only with a caught \\Throwable") + .php_class_name(), e ))); } @@ -960,13 +966,10 @@ impl DiagnoseCommand { PHP_EOL, PHP_EOL ), other => { - return Err(InvalidArgumentException { - message: format!( - "DiagnoseCommand: Unknown error type \"{}\". Please report at https://github.com/composer/composer/issues/new.", - other, - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "DiagnoseCommand: Unknown error type \"{}\". Please report at https://github.com/composer/composer/issues/new.", + other, + )) .into()); } }; @@ -1042,13 +1045,10 @@ impl DiagnoseCommand { PHP_EOL ), other => { - return Err(InvalidArgumentException { - message: format!( - "DiagnoseCommand: Unknown warning type \"{}\". Please report at https://github.com/composer/composer/issues/new.", - other, - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "DiagnoseCommand: Unknown warning type \"{}\". Please report at https://github.com/composer/composer/issues/new.", + other, + )) .into()); } }; @@ -1409,13 +1409,19 @@ impl Command for DiagnoseCommand { Ok(()) })(); if let Err(e) = proxy_check_result { - if let Some(_te) = e.downcast_ref::<TransportException>() { + if let Some(_te) = e.catch::<TransportException>() { io.write_no_newline("Checking HTTP proxy: "); let status = self.check_connectivity_and_composer_network_http_enablement(); self.output_result(if is_string(&status) { status } else { - PhpMixed::String(format!("<error>[{}] {}</error>", get_class_err(&e), e)) + PhpMixed::String(format!( + "<error>[{}] {}</error>", + AnyThrowable::of(e.as_ref()) + .expect("PHP reaches this only with a caught \\Throwable") + .php_class_name(), + e + )) }); } else { return Err(e); @@ -1455,20 +1461,24 @@ impl Command for DiagnoseCommand { } } Err(e) => { - if let Some(te) = e.downcast_ref::<TransportException>() { + if let Some(te) = e.catch::<TransportException>() { if te.get_code() == 401 { self.output_result(PhpMixed::String("<comment>The oauth token for github.com seems invalid, run \"composer config --global --unset github-oauth.github.com\" to remove it</comment>".to_string())); } else { self.output_result(PhpMixed::String(format!( "<error>[{}] {}</error>", - get_class_err(&e), + AnyThrowable::of(e.as_ref()) + .expect("PHP reaches this only with a caught \\Throwable") + .php_class_name(), e ))); } } else { self.output_result(PhpMixed::String(format!( "<error>[{}] {}</error>", - get_class_err(&e), + AnyThrowable::of(e.as_ref()) + .expect("PHP reaches this only with a caught \\Throwable") + .php_class_name(), e ))); } diff --git a/crates/shirabe/src/command/dump_autoload_command.rs b/crates/shirabe/src/command/dump_autoload_command.rs index 8aad0705..f9be4cff 100644 --- a/crates/shirabe/src/command/dump_autoload_command.rs +++ b/crates/shirabe/src/command/dump_autoload_command.rs @@ -149,10 +149,7 @@ impl Command for DumpAutoloadCommand { && !optimize && !authoritative { - return Err(InvalidArgumentException { - message: "--strict-psr mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new("--strict-psr mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.".to_string()) .into()); } if input @@ -163,10 +160,7 @@ impl Command for DumpAutoloadCommand { && !optimize && !authoritative { - return Err(InvalidArgumentException { - message: "--strict-ambiguous mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new("--strict-ambiguous mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.".to_string()) .into()); } @@ -208,12 +202,10 @@ impl Command for DumpAutoloadCommand { .as_bool() .unwrap_or(false) { - return Err(InvalidArgumentException { - message: - "You can not use both --no-dev and --dev as they conflict with each other." - .to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "You can not use both --no-dev and --dev as they conflict with each other." + .to_string(), + ) .into()); } composer diff --git a/crates/shirabe/src/command/exec_command.rs b/crates/shirabe/src/command/exec_command.rs index d05b6c58..a67c234e 100644 --- a/crates/shirabe/src/command/exec_command.rs +++ b/crates/shirabe/src/command/exec_command.rs @@ -174,13 +174,10 @@ impl Command for ExecCommand { .as_string() .unwrap_or("") .to_string(); - return Err(RuntimeException { - message: format!( - "No binaries found in composer.json or in bin-dir ({})", - bin_dir - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "No binaries found in composer.json or in bin-dir ({})", + bin_dir + )) .into()); } @@ -222,9 +219,11 @@ impl Command for ExecCommand { if let Some(ref iwd) = initial_working_directory && getcwd().as_deref() != Some(iwd.as_str()) { - chdir(iwd).map_err(|e| RuntimeException { - message: format!("Could not switch back to working directory \"{}\"", iwd), - code: 0, + chdir(iwd).map_err(|e| { + RuntimeException::new(format!( + "Could not switch back to working directory \"{}\"", + iwd + )) })?; } diff --git a/crates/shirabe/src/command/global_command.rs b/crates/shirabe/src/command/global_command.rs index 79037e55..78b6f3e6 100644 --- a/crates/shirabe/src/command/global_command.rs +++ b/crates/shirabe/src/command/global_command.rs @@ -61,11 +61,10 @@ impl GlobalCommand { } else if let Some(completion_input) = input_any.downcast_ref::<CompletionInput>() { Ok(completion_input.to_string()) } else { - Err(LogicException { - message: "Expected an Input instance that is stringable".to_string(), - code: 0, - } - .into()) + Err( + LogicException::new("Expected an Input instance that is stringable".to_string()) + .into(), + ) } } @@ -85,17 +84,14 @@ impl GlobalCommand { let mut fs = Filesystem::new(None); fs.ensure_directory_exists(&home)?; if !Path::new(&home).is_dir() { - return Err(RuntimeException { - message: "Could not create home directory".to_string(), - code: 0, - } - .into()); + return Err( + RuntimeException::new("Could not create home directory".to_string()).into(), + ); } } - chdir(&home).map_err(|_e| RuntimeException { - message: format!("Could not switch to home directory \"{}\"", home), - code: 0, + chdir(&home).map_err(|_e| { + RuntimeException::new(format!("Could not switch to home directory \"{}\"", home)) })?; if !quiet { diff --git a/crates/shirabe/src/command/init_command.rs b/crates/shirabe/src/command/init_command.rs index 5168b2ba..7b4012ce 100644 --- a/crates/shirabe/src/command/init_command.rs +++ b/crates/shirabe/src/command/init_command.rs @@ -25,6 +25,7 @@ use shirabe_external_packages::symfony::console::helper::FormatBlockMessages; use shirabe_external_packages::symfony::console::input::ArrayInput; use shirabe_external_packages::symfony::console::input::InputInterface; use shirabe_external_packages::symfony::console::output::OutputInterface; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ FILE_IGNORE_NEW_LINES, InvalidArgumentException, PHP_EOL, PHP_SERVER, PhpMixed, array_flip_strings, array_intersect_key, array_map, basename, empty, explode, file, @@ -99,11 +100,9 @@ impl InitCommand { if let Some(ref email) = email && !self.is_valid_email(email) { - return Err(InvalidArgumentException { - message: format!("Invalid email \"{}\"", email), - code: 0, - } - .into()); + return Err( + InvalidArgumentException::new(format!("Invalid email \"{}\"", email)).into(), + ); } let mut result: IndexMap<String, Option<String>> = IndexMap::new(); @@ -121,11 +120,8 @@ impl InitCommand { return Ok(result); } - Err(InvalidArgumentException { - message: "Invalid author string. Must be in the formats: Jane Doe or John Smith <john@example.com>" - .to_string(), - code: 0, - } + Err(InvalidArgumentException::new("Invalid author string. Must be in the formats: Jane Doe or John Smith <john@example.com>" + .to_string()) .into()) } @@ -517,13 +513,10 @@ impl Command for InitCommand { .unwrap_or(""), ) { - return Err(InvalidArgumentException { - message: format!( - "The package name {} is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+", - options.get("name").and_then(|v| v.as_string()).unwrap_or("") - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "The package name {} is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+", + options.get("name").and_then(|v| v.as_string()).unwrap_or("") + )) .into()); } @@ -678,7 +671,7 @@ impl Command for InitCommand { let validate_result = file_obj.validate_schema(JsonFile::LAX_SCHEMA, None); if let Err(e) = validate_result { // try to downcast to JsonValidationException - if let Some(json_err) = e.downcast_ref::<JsonValidationException>() { + if let Some(json_err) = e.catch::<JsonValidationException>() { io.write_error3( "<error>Schema validation error, aborting</error>", true, @@ -926,13 +919,10 @@ impl Command for InitCommand { php_regex!(r"{^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$}D"), value.as_string().unwrap_or(""), ) { - return Err(InvalidArgumentException { - message: format!( - "The package name {} is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+", - value.as_string().unwrap_or("") - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "The package name {} is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+", + value.as_string().unwrap_or("") + )) .into()); } @@ -1031,20 +1021,17 @@ impl Command for InitCommand { } if !base_package::STABILITIES.contains_key(value.as_string().unwrap_or("")) { - return Err(InvalidArgumentException { - message: format!( - "Invalid minimum stability \"{}\". Must be empty or one of: {}", - value.as_string().unwrap_or(""), - implode( - ", ", - &base_package::STABILITIES - .keys() - .map(|k| k.to_string()) - .collect::<Vec<_>>() - ) - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Invalid minimum stability \"{}\". Must be empty or one of: {}", + value.as_string().unwrap_or(""), + implode( + ", ", + &base_package::STABILITIES + .keys() + .map(|k| k.to_string()) + .collect::<Vec<_>>() + ) + )) .into()); } @@ -1107,13 +1094,10 @@ impl Command for InitCommand { && !spdx.validate(license.as_string().unwrap_or("")) && license.as_string() != Some("proprietary") { - return Err(InvalidArgumentException { - message: format!( + return Err(InvalidArgumentException::new(format!( "Invalid license provided: {}. Only SPDX license identifiers (https://spdx.org/licenses/) or \"proprietary\" are accepted.", license.as_string().unwrap_or("") - ), - code: 0, - } + )) .into()); } input.borrow_mut().set_option("license", license); @@ -1246,13 +1230,10 @@ impl Command for InitCommand { if !Preg::is_match(php_regex!(r"{^[^/][A-Za-z0-9\-_/]+/$}"), &value_or_default) { - return Err(InvalidArgumentException { - message: format!( - "The src folder name \"{}\" is invalid. Please add a relative path with tailing forward slash. [A-Za-z0-9_-/]+/", - value_or_default, - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "The src folder name \"{}\" is invalid. Please add a relative path with tailing forward slash. [A-Za-z0-9_-/]+/", + value_or_default, + )) .into()); } diff --git a/crates/shirabe/src/command/licenses_command.rs b/crates/shirabe/src/command/licenses_command.rs index 41e9ba90..1b2ec6b6 100644 --- a/crates/shirabe/src/command/licenses_command.rs +++ b/crates/shirabe/src/command/licenses_command.rs @@ -128,10 +128,7 @@ impl Command for LicensesCommand { let locker = composer.get_locker().clone(); let mut locker = locker.borrow_mut(); if !locker.is_locked() { - return Err(UnexpectedValueException { - message: "Valid composer.json and composer.lock files are required to run this command with --locked".to_string(), - code: 0, - }.into()); + return Err(UnexpectedValueException::new("Valid composer.json and composer.lock files are required to run this command with --locked".to_string()).into()); } let no_dev = input .borrow() @@ -314,13 +311,10 @@ impl Command for LicensesCommand { ); } _ => { - return Err(RuntimeException { - message: format!( - "Unsupported format \"{}\". See help for supported formats.", - format - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Unsupported format \"{}\". See help for supported formats.", + format + )) .into()); } } diff --git a/crates/shirabe/src/command/package_discovery_trait.rs b/crates/shirabe/src/command/package_discovery_trait.rs index db7db488..17332083 100644 --- a/crates/shirabe/src/command/package_discovery_trait.rs +++ b/crates/shirabe/src/command/package_discovery_trait.rs @@ -22,6 +22,7 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; use shirabe_external_packages::symfony::console::input::InputInterface; use shirabe_external_packages::symfony::console::output::OutputInterface; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ Exception, InvalidArgumentException, LogicException, PHP_EOL, PhpMixed, array_keys, array_slice, asort, explode, file_get_contents, implode, in_array_strict, is_array, is_file, @@ -359,11 +360,7 @@ pub trait PackageDiscoveryTrait: BaseCommand { )); } - Err(Exception { - message: "Not a valid selection".to_string(), - code: 0, - } - .into()) + Err(Exception::new("Not a valid selection".to_string()).into()) }, ); @@ -541,17 +538,14 @@ pub trait PackageDiscoveryTrait: BaseCommand { ShowWarnings::Always, )?; if let Some(candidate) = candidate { - return Err(InvalidArgumentException { - message: format!( - "Package {} has requirements incompatible with your PHP version, PHP extensions and Composer version{}", - name, - self.get_platform_exception_details( - candidate, - platform_repo, - )?, - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package {} has requirements incompatible with your PHP version, PHP extensions and Composer version{}", + name, + self.get_platform_exception_details( + candidate, + platform_repo, + )?, + )) .into()); } } @@ -577,34 +571,28 @@ pub trait PackageDiscoveryTrait: BaseCommand { ShowWarnings::Always, )?; if let Some(all_repos_package) = all_repos_package { - return Err(InvalidArgumentException { - message: format!( - "Package {} exists in {} and {} which has a higher repository priority. The packages from the higher priority repository do not match your minimum-stability and are therefore not installable. That repository is canonical so the lower priority repo's packages are not installable. See https://getcomposer.org/repoprio for details and assistance.", - name, - all_repos_package - .get_repository() - .map(|r| r.get_repo_name()) - .transpose()? - .unwrap_or_default(), - package - .get_repository() - .map(|r| r.get_repo_name()) - .transpose()? - .unwrap_or_default(), - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package {} exists in {} and {} which has a higher repository priority. The packages from the higher priority repository do not match your minimum-stability and are therefore not installable. That repository is canonical so the lower priority repo's packages are not installable. See https://getcomposer.org/repoprio for details and assistance.", + name, + all_repos_package + .get_repository() + .map(|r| r.get_repo_name()) + .transpose()? + .unwrap_or_default(), + package + .get_repository() + .map(|r| r.get_repo_name()) + .transpose()? + .unwrap_or_default(), + )) .into()); } - return Err(InvalidArgumentException { - message: format!( - "Could not find a version of package {} matching your minimum-stability ({}). Require it with an explicit version constraint allowing its desired stability.", - name, - effective_minimum_stability, - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Could not find a version of package {} matching your minimum-stability ({}). Require it with an explicit version constraint allowing its desired stability.", + name, + effective_minimum_stability, + )) .into()); } // Check whether the PHP version was the problem for all versions @@ -639,18 +627,15 @@ pub trait PackageDiscoveryTrait: BaseCommand { ); } - return Err(InvalidArgumentException { - message: format!( - "Could not find package {} in any version matching your PHP version, PHP extensions and Composer version{}{}", - name, - self.get_platform_exception_details( - candidate, - platform_repo, - )?, - additional, - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Could not find package {} in any version matching your PHP version, PHP extensions and Composer version{}{}", + name, + self.get_platform_exception_details( + candidate, + platform_repo, + )?, + additional, + )) .into()); } } @@ -665,13 +650,10 @@ pub trait PackageDiscoveryTrait: BaseCommand { .map(|s| PhpMixed::String(s.clone())) .collect::<Vec<_>>(), ) { - return Err(InvalidArgumentException { - message: format!( - "Could not find package {}. It was however found via repository search, which indicates a consistency issue with the repository.", - name, - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Could not find package {}. It was however found via repository search, which indicates a consistency issue with the repository.", + name, + )) .into()); } @@ -704,30 +686,24 @@ pub trait PackageDiscoveryTrait: BaseCommand { } } - return Err(InvalidArgumentException { - message: format!( - "Could not find package {}.\n\nDid you mean {}?\n {}", - name, - if similar.len() > 1 { - "one of these" - } else { - "this" - }, - implode("\n ", &similar), - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Could not find package {}.\n\nDid you mean {}?\n {}", + name, + if similar.len() > 1 { + "one of these" + } else { + "this" + }, + implode("\n ", &similar), + )) .into()); } - return Err(InvalidArgumentException { - message: format!( - "Could not find a matching version of package {}. Check the package spelling, your version constraint and that the package is available in a stability which matches your minimum-stability ({}).", - name, - effective_minimum_stability, - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Could not find a matching version of package {}. Check the package spelling, your version constraint and that the package is available in a stability which matches your minimum-stability ({}).", + name, + effective_minimum_stability, + )) .into()); } @@ -745,11 +721,9 @@ pub trait PackageDiscoveryTrait: BaseCommand { fn find_similar(&self, package: &str) -> anyhow::Result<Vec<String>> { let results: Vec<SearchResult> = match (|| -> anyhow::Result<Vec<SearchResult>> { if self.get_repos_mut().is_none() { - return Err(LogicException { - message: "findSimilar was called before $this->repos was initialized" - .to_string(), - code: 0, - } + return Err(LogicException::new( + "findSimilar was called before $this->repos was initialized".to_string(), + ) .into()); } self.get_repos_mut() @@ -760,7 +734,7 @@ pub trait PackageDiscoveryTrait: BaseCommand { Ok(r) => r, Err(e) => { // PHP: if ($e instanceof \LogicException) throw $e; - if e.downcast_ref::<LogicException>().is_some() { + if e.is_instanceof::<LogicException>() { return Err(e); } diff --git a/crates/shirabe/src/command/reinstall_command.rs b/crates/shirabe/src/command/reinstall_command.rs index b16b6c92..f9753f2a 100644 --- a/crates/shirabe/src/command/reinstall_command.rs +++ b/crates/shirabe/src/command/reinstall_command.rs @@ -98,12 +98,10 @@ impl Command for ReinstallCommand { if type_count > 0 { if packages_count > 0 { - return Err(InvalidArgumentException { - message: - "You cannot specify package names and filter by type at the same time." - .to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "You cannot specify package names and filter by type at the same time." + .to_string(), + ) .into()); } let filter_types: Vec<String> = type_option @@ -122,11 +120,9 @@ impl Command for ReinstallCommand { } } else { if packages_count == 0 { - return Err(InvalidArgumentException { - message: "You must pass one or more package names to be reinstalled." - .to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "You must pass one or more package names to be reinstalled.".to_string(), + ) .into()); } let patterns: Vec<String> = packages_arg diff --git a/crates/shirabe/src/command/remove_command.rs b/crates/shirabe/src/command/remove_command.rs index 77deaf14..e89e9535 100644 --- a/crates/shirabe/src/command/remove_command.rs +++ b/crates/shirabe/src/command/remove_command.rs @@ -193,12 +193,10 @@ impl Command for RemoveCommand { .as_bool() .unwrap_or(false) { - return Err(anyhow::anyhow!(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: "Not enough arguments (missing: \"packages\").".to_string(), - code: 0, - } - ))); + return Err(InvalidArgumentException::new( + "Not enough arguments (missing: \"packages\").".to_string(), + ) + .into()); } let mut packages: Vec<String> = input @@ -224,12 +222,11 @@ impl Command for RemoveCommand { let locker = composer.get_locker().clone(); let mut locker = locker.borrow_mut(); if !locker.is_locked() { - return Err(anyhow::anyhow!(UnexpectedValueException { - message: - "A valid composer.lock file is required to run this command with --unused" - .to_string(), - code: 0, - })); + return Err(UnexpectedValueException::new( + "A valid composer.lock file is required to run this command with --unused" + .to_string(), + ) + .into()); } } diff --git a/crates/shirabe/src/command/repository_command.rs b/crates/shirabe/src/command/repository_command.rs index ab2cf2d4..47fd2d28 100644 --- a/crates/shirabe/src/command/repository_command.rs +++ b/crates/shirabe/src/command/repository_command.rs @@ -362,26 +362,20 @@ impl Command for RepositoryCommand { } "add" => { if name.is_none() { - return Err(anyhow::anyhow!(RuntimeException { - message: "You must pass a repository name. Example: composer repo add foo vcs https://example.org".to_string(), - code: 0, - })); + return Err(RuntimeException::new("You must pass a repository name. Example: composer repo add foo vcs https://example.org".to_string()).into()); } if arg1.is_none() { - return Err(anyhow::anyhow!(RuntimeException { - message: "You must pass the type and a url, or a JSON string.".to_string(), - code: 0, - })); + return Err(RuntimeException::new( + "You must pass the type and a url, or a JSON string.".to_string(), + ) + .into()); } let arg1_str = arg1.as_deref().unwrap(); let repo_config: PhpMixed = if Preg::is_match(php_regex!(r"{^\s*\{}"), arg1_str) { JsonFile::parse_json(Some(arg1_str), None)? } else { if arg2.is_none() { - return Err(anyhow::anyhow!(RuntimeException { - message: "You must pass the type and a url. Example: composer repo add foo vcs https://example.org".to_string(), - code: 0, - })); + return Err(RuntimeException::new("You must pass the type and a url. Example: composer repo add foo vcs https://example.org".to_string()).into()); } let mut m = IndexMap::new(); m.insert("type".to_string(), PhpMixed::String(arg1_str.to_string())); @@ -400,19 +394,19 @@ impl Command for RepositoryCommand { .as_string() .map(|s| s.to_string()); if before.is_some() && after.is_some() { - return Err(anyhow::anyhow!(RuntimeException { - message: "You can not combine --before and --after".to_string(), - code: 0, - })); + return Err(RuntimeException::new( + "You can not combine --before and --after".to_string(), + ) + .into()); } if before.is_some() || after.is_some() { if matches!(repo_config, PhpMixed::Bool(false)) { - return Err(anyhow::anyhow!(RuntimeException { - message: "Cannot use --before/--after with boolean repository values" + return Err(RuntimeException::new( + "Cannot use --before/--after with boolean repository values" .to_string(), - code: 0, - })); + ) + .into()); } let reference_name = before.as_deref().or(after.as_deref()).unwrap(); let offset: i64 = if after.is_some() { 1 } else { 0 }; @@ -443,10 +437,10 @@ impl Command for RepositoryCommand { } "remove" | "rm" | "delete" => { if name.is_none() { - return Err(anyhow::anyhow!(RuntimeException { - message: "You must pass the repository name to remove.".to_string(), - code: 0, - })); + return Err(RuntimeException::new( + "You must pass the repository name to remove.".to_string(), + ) + .into()); } let name_str = name.as_deref().unwrap(); self.config_source @@ -465,10 +459,10 @@ impl Command for RepositoryCommand { } "set-url" | "seturl" => { if name.is_none() || arg1.is_none() { - return Err(anyhow::anyhow!(RuntimeException { - message: "Usage: composer repo set-url <name> <new-url>".to_string(), - code: 0, - })); + return Err(RuntimeException::new( + "Usage: composer repo set-url <name> <new-url>".to_string(), + ) + .into()); } self.config_source .borrow_mut() @@ -479,10 +473,10 @@ impl Command for RepositoryCommand { } "get-url" | "geturl" => { if name.is_none() { - return Err(anyhow::anyhow!(RuntimeException { - message: "Usage: composer repo get-url <name>".to_string(), - code: 0, - })); + return Err(RuntimeException::new( + "Usage: composer repo get-url <name>".to_string(), + ) + .into()); } let name_str = name.as_deref().unwrap(); if let Some(repo) = repos.get(name_str) @@ -493,10 +487,11 @@ impl Command for RepositoryCommand { self.get_io().write(url); return Ok(0); } - return Err(anyhow::anyhow!(InvalidArgumentException { - message: format!("The {} repository does not have a URL", name_str), - code: 0, - })); + return Err(InvalidArgumentException::new(format!( + "The {} repository does not have a URL", + name_str + )) + .into()); } for (_key, val) in &repos { if let PhpMixed::Array(ref repo_map) = *val @@ -508,23 +503,25 @@ impl Command for RepositoryCommand { self.get_io().write(url); return Ok(0); } - return Err(anyhow::anyhow!(InvalidArgumentException { - message: format!("The {} repository does not have a URL", name_str), - code: 0, - })); + return Err(InvalidArgumentException::new(format!( + "The {} repository does not have a URL", + name_str + )) + .into()); } } - Err(anyhow::anyhow!(InvalidArgumentException { - message: format!("There is no {} repository defined", name_str), - code: 0, - })) + Err(InvalidArgumentException::new(format!( + "There is no {} repository defined", + name_str + )) + .into()) } "disable" => { if name.is_none() { - return Err(anyhow::anyhow!(RuntimeException { - message: "Usage: composer repo disable packagist.org".to_string(), - code: 0, - })); + return Err(RuntimeException::new( + "Usage: composer repo disable packagist.org".to_string(), + ) + .into()); } let name_str = name.as_deref().unwrap(); if ["packagist", "packagist.org"].contains(&name_str) { @@ -540,17 +537,14 @@ impl Command for RepositoryCommand { .add_repository("packagist.org", PhpMixed::Bool(false), append); return Ok(0); } - Err(anyhow::anyhow!(RuntimeException { - message: "Only packagist.org can be enabled/disabled using this command. Use add/remove for other repositories.".to_string(), - code: 0, - })) + Err(RuntimeException::new("Only packagist.org can be enabled/disabled using this command. Use add/remove for other repositories.".to_string()).into()) } "enable" => { if name.is_none() { - return Err(anyhow::anyhow!(RuntimeException { - message: "Usage: composer repo enable packagist.org".to_string(), - code: 0, - })); + return Err(RuntimeException::new( + "Usage: composer repo enable packagist.org".to_string(), + ) + .into()); } let name_str = name.as_deref().unwrap(); if ["packagist", "packagist.org"].contains(&name_str) { @@ -561,19 +555,16 @@ impl Command for RepositoryCommand { .remove_repository("packagist.org"); return Ok(0); } - Err(anyhow::anyhow!(RuntimeException { - message: "Only packagist.org can be enabled/disabled using this command." - .to_string(), - code: 0, - })) + Err(RuntimeException::new( + "Only packagist.org can be enabled/disabled using this command.".to_string(), + ) + .into()) } - _ => Err(anyhow::anyhow!(InvalidArgumentException { - message: format!( - "Unknown action \"{}\". Use list, add, remove, set-url, get-url, enable, disable", - action - ), - code: 0, - })), + _ => Err(InvalidArgumentException::new(format!( + "Unknown action \"{}\". Use list, add, remove, set-url, get-url, enable, disable", + action + )) + .into()), } } diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs index 85e53a37..84b4a6e4 100644 --- a/crates/shirabe/src/command/require_command.rs +++ b/crates/shirabe/src/command/require_command.rs @@ -977,13 +977,10 @@ impl Command for RequireCommand { if self.newly_created.get() { self.revert_composer_file(); - return Err(RuntimeException { - message: format!( - "No composer.json present in the current directory ({}), this may be the cause of the following exception.", - self.file.borrow() - ), - code: 0, - } + 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() + )) .into()); } diff --git a/crates/shirabe/src/command/run_script_command.rs b/crates/shirabe/src/command/run_script_command.rs index fe020de3..be50c4be 100644 --- a/crates/shirabe/src/command/run_script_command.rs +++ b/crates/shirabe/src/command/run_script_command.rs @@ -16,6 +16,7 @@ use shirabe_external_packages::symfony::console::exception::CommandNotFoundExcep use shirabe_external_packages::symfony::console::exception::namespace_not_found_exception::NamespaceNotFoundException; use shirabe_external_packages::symfony::console::input::InputInterface; use shirabe_external_packages::symfony::console::output::OutputInterface; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{InvalidArgumentException, RuntimeException}; use shirabe_php_shim::{PhpMixed, impl_php_class}; @@ -102,8 +103,8 @@ impl RunScriptCommand { match application.borrow_mut().find(&name) { Ok(cmd) => description = cmd.borrow().get_description(), Err(e) - if e.downcast_ref::<CommandNotFoundException>().is_some() - || e.downcast_ref::<NamespaceNotFoundException>().is_some() => {} + if e.is_instanceof::<CommandNotFoundException>() + || e.is_instanceof::<NamespaceNotFoundException>() => {} Err(e) => return Err(e), } } @@ -255,10 +256,9 @@ impl Command for RunScriptCommand { let script = match input.borrow().get_argument("script")?.as_string() { None => { - return Err(RuntimeException { - message: "Missing required argument \"script\"".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "Missing required argument \"script\"".to_string(), + ) .into()); } Some(s) => s.to_string(), @@ -267,10 +267,10 @@ impl Command for RunScriptCommand { if !self.script_events.contains(&script.as_str()) { let const_name = script.to_uppercase().replace('-', "_"); if ScriptEvents::is_defined(&const_name) { - return Err(InvalidArgumentException { - message: format!("Script \"{}\" cannot be run with this command", script), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Script \"{}\" cannot be run with this command", + script + )) .into()); } } @@ -299,10 +299,10 @@ impl Command for RunScriptCommand { ); let has_listeners = dispatcher.borrow_mut().has_event_listeners(&event); if !has_listeners { - return Err(InvalidArgumentException { - message: format!("Script \"{}\" is not defined in this package", script), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Script \"{}\" is not defined in this package", + script + )) .into()); } @@ -320,12 +320,10 @@ impl Command for RunScriptCommand { if let Some(timeout_val) = input.borrow().get_option("timeout")?.as_string() { let timeout_str = timeout_val.to_string(); if !timeout_str.chars().all(|c| c.is_ascii_digit()) { - return Err(RuntimeException { - message: - "Timeout value must be numeric and positive if defined, or 0 for forever" - .to_string(), - code: 0, - } + return Err(RuntimeException::new( + "Timeout value must be numeric and positive if defined, or 0 for forever" + .to_string(), + ) .into()); } let timeout: i64 = timeout_str.parse().unwrap_or(0); diff --git a/crates/shirabe/src/command/script_alias_command.rs b/crates/shirabe/src/command/script_alias_command.rs index e6a13545..e3faf3a6 100644 --- a/crates/shirabe/src/command/script_alias_command.rs +++ b/crates/shirabe/src/command/script_alias_command.rs @@ -36,12 +36,10 @@ impl ScriptAliasCommand { for alias in &aliases { if !is_string(&PhpMixed::String(alias.clone())) { - return Err(InvalidArgumentException { - message: - r#""scripts-aliases" element array values should contain only strings"# - .to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + r#""scripts-aliases" element array values should contain only strings"# + .to_string(), + ) .into()); } } @@ -123,10 +121,9 @@ impl Command for ScriptAliasCommand { // TODO(phase-c): InputInterface has_to_string/get_class_name not modeled in Rust // TODO remove for Symfony 6+ as it is then in the interface if false { - return Err(LogicException { - message: "Expected an Input instance that is stringable".to_string(), - code: 0, - } + return Err(LogicException::new( + "Expected an Input instance that is stringable".to_string(), + ) .into()); } diff --git a/crates/shirabe/src/command/search_command.rs b/crates/shirabe/src/command/search_command.rs index 4bf75c8b..51432ed9 100644 --- a/crates/shirabe/src/command/search_command.rs +++ b/crates/shirabe/src/command/search_command.rs @@ -181,10 +181,9 @@ impl Command for SearchCommand { .as_bool() .unwrap_or(false) { - return Err(InvalidArgumentException { - message: "--only-name and --only-vendor cannot be used together".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "--only-name and --only-vendor cannot be used together".to_string(), + ) .into()); } mode = repository_interface::SEARCH_NAME; diff --git a/crates/shirabe/src/command/show_command.rs b/crates/shirabe/src/command/show_command.rs index aa63d8e7..6c693e62 100644 --- a/crates/shirabe/src/command/show_command.rs +++ b/crates/shirabe/src/command/show_command.rs @@ -359,14 +359,11 @@ impl ShowCommand { if let Some(ref mp) = matched_package && mp.as_complete().is_none() { - return Err(LogicException { - message: format!( - "ShowCommand::getPackage can only work with CompletePackageInterface, but got {}", - shirabe_php_shim::get_class(&PhpMixed::Null) - ), - code: 0, - } - .into()); + return Err(LogicException::new(format!( + "ShowCommand::getPackage can only work with CompletePackageInterface, but got {}", + shirabe_php_shim::get_class(&PhpMixed::Null) + )) + .into()); } let matched_package = matched_package.and_then(|mp| mp.as_complete()); @@ -1817,10 +1814,9 @@ impl Command for ShowCommand { .as_string() .is_some() { - return Err(InvalidArgumentException { - message: "You cannot use --self together with a package name".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "You cannot use --self together with a package name".to_string(), + ) .into()); } installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![ @@ -1931,10 +1927,7 @@ impl Command for ShowCommand { .borrow_mut() .is_locked() { - return Err(UnexpectedValueException { - message: "A valid composer.json and composer.lock files is required to run this command with --locked".to_string(), - code: 0, - } + return Err(UnexpectedValueException::new("A valid composer.json and composer.lock files is required to run this command with --locked".to_string()) .into()); } let composer_ref = crate::composer::composer_full(composer.as_ref().unwrap()); @@ -2090,14 +2083,11 @@ impl Command for ShowCommand { .collect::<Vec<_>>(), ) { - return Err(InvalidArgumentException { - message: format!( - "Package \"{}\" is installed but not a direct dependent of the root package.", - pkg.get_name() - ), - code: 0, - } - .into()); + return Err(InvalidArgumentException::new(format!( + "Package \"{}\" is installed but not a direct dependent of the root package.", + pkg.get_name() + )) + .into()); } if matched_package.is_none() { @@ -2123,10 +2113,10 @@ impl Command for ShowCommand { hint.push_str(", try using --available (-a) to show all available packages"); } - return Err(InvalidArgumentException { - message: format!("Package \"{}\" not found{}.", pf, hint), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package \"{}\" not found{}.", + pf, hint + )) .into()); } single_package = matched_package; diff --git a/crates/shirabe/src/command/update_command.rs b/crates/shirabe/src/command/update_command.rs index c928552a..02f32526 100644 --- a/crates/shirabe/src/command/update_command.rs +++ b/crates/shirabe/src/command/update_command.rs @@ -72,10 +72,9 @@ impl UpdateCommand { packages: Vec<String>, ) -> anyhow::Result<Vec<String>> { if !input.borrow().is_interactive() { - return Err(InvalidArgumentException { - message: "--interactive cannot be used in non-interactive terminals.".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "--interactive cannot be used in non-interactive terminals.".to_string(), + ) .into()); } @@ -165,10 +164,9 @@ impl UpdateCommand { } if autocompleter_values.is_empty() { - return Err(RuntimeException { - message: "Could not find any package with new versions available".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "Could not find any package with new versions available".to_string(), + ) .into()); } @@ -210,11 +208,7 @@ impl UpdateCommand { return Ok(packages); } - Err(RuntimeException { - message: "Installation aborted.".to_string(), - code: 0, - } - .into()) + Err(RuntimeException::new("Installation aborted.".to_string()).into()) } fn create_version_selector( @@ -450,10 +444,9 @@ impl Command for UpdateCommand { .unwrap_or(false) { if !composer.get_locker().borrow_mut().is_locked() { - return Err(InvalidArgumentException { - message: "patch-only can only be used with a lock file present".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "patch-only can only be used with a lock file present".to_string(), + ) .into()); } for package in composer diff --git a/crates/shirabe/src/config.rs b/crates/shirabe/src/config.rs index 154fbd05..300b9bfa 100644 --- a/crates/shirabe/src/config.rs +++ b/crates/shirabe/src/config.rs @@ -658,10 +658,10 @@ impl Config { &raw, Some(&mut matches), ) { - return Err(RuntimeException { - message: format!("Could not parse the value of '{}': {}", key, raw), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not parse the value of '{}': {}", + key, raw + )) .into()); } let mut size = matches @@ -735,13 +735,10 @@ impl Config { PhpMixed::String("symlink".to_string()), ], ) { - return Err(RuntimeException { - message: format!( - "Invalid value for 'bin-compat': {}. Expected auto, full or proxy", - value - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Invalid value for 'bin-compat': {}. Expected auto, full or proxy", + value + )) .into()); } @@ -760,13 +757,10 @@ impl Config { if !matches!(env, PhpMixed::Bool(false)) { let env_str = env.as_string().unwrap_or("").to_string(); if !matches!(env_str.as_str(), "stash" | "true" | "false" | "1" | "0") { - return Err(RuntimeException { - message: format!( - "Invalid value for COMPOSER_DISCARD_CHANGES: {}. Expected 1, 0, true, false or stash", - env_str - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Invalid value for COMPOSER_DISCARD_CHANGES: {}. Expected 1, 0, true, false or stash", + env_str + )) .into()); } if env_str == "stash" { @@ -782,13 +776,10 @@ impl Config { let val = self.config.get(key).cloned().unwrap_or(PhpMixed::Null); let allowed = matches!(&val, PhpMixed::Bool(_)) || val.as_string() == Some("stash"); if !allowed { - return Err(RuntimeException { - message: format!( - "Invalid value for 'discard-changes': {:?}. Expected true, false or stash", - val - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Invalid value for 'discard-changes': {:?}. Expected true, false or stash", + val + )) .into()); } @@ -824,10 +815,7 @@ impl Config { } let first = protos.first().cloned(); if first.as_deref() == Some("http") { - return Err(RuntimeException { - message: "The http protocol for github is not available anymore, update your config's github-protocols to use \"https\", \"git\" or \"ssh\"".to_string(), - code: 0, - } + return Err(RuntimeException::new("The http protocol for github is not available anymore, update your config's github-protocols to use \"https\", \"git\" or \"ssh\"".to_string()) .into()); } @@ -860,14 +848,11 @@ impl Config { .map(|s| PhpMixed::String(s.clone())) .collect::<Vec<_>>(), ) { - return Err(RuntimeException { - message: format!( - "Invalid value for COMPOSER_AUDIT_ABANDONED: {}. Expected one of {}.", - abandoned_env_str, - implode(", ", &valid_choices), - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Invalid value for COMPOSER_AUDIT_ABANDONED: {}. Expected one of {}.", + abandoned_env_str, + implode(", ", &valid_choices), + )) .into()); } if let PhpMixed::Array(ref mut m) = result { @@ -880,13 +865,10 @@ impl Config { if !matches!(block_abandoned_env, PhpMixed::Bool(false)) { let env_str = block_abandoned_env.as_string().unwrap_or("").to_string(); if !matches!(env_str.as_str(), "0" | "1") { - return Err(RuntimeException { - message: format!( - "Invalid value for COMPOSER_SECURITY_BLOCKING_ABANDONED: {}. Expected 0 or 1.", - env_str - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Invalid value for COMPOSER_SECURITY_BLOCKING_ABANDONED: {}. Expected 0 or 1.", + env_str + )) .into()); } if let PhpMixed::Array(ref mut m) = result { diff --git a/crates/shirabe/src/config/json_config_source.rs b/crates/shirabe/src/config/json_config_source.rs index 53e3722b..0257ee31 100644 --- a/crates/shirabe/src/config/json_config_source.rs +++ b/crates/shirabe/src/config/json_config_source.rs @@ -7,6 +7,7 @@ use crate::json::JsonValidationException; use crate::util::Filesystem; use crate::util::Silencer; use indexmap::IndexMap; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ PHP_EOL, PhpMixed, RuntimeException, chmod, explode, file_get_contents, file_put_contents, implode, is_writable, @@ -32,24 +33,18 @@ impl JsonConfigSource { let contents; if self.file.borrow().exists() { if !is_writable(self.file.borrow().get_path()) { - return Err(RuntimeException { - message: format!( - "The file \"{}\" is not writable.", - self.file.borrow().get_path(), - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "The file \"{}\" is not writable.", + self.file.borrow().get_path(), + )) .into()); } if !Filesystem::is_readable(self.file.borrow().get_path()) { - return Err(RuntimeException { - message: format!( - "The file \"{}\" is not readable.", - self.file.borrow().get_path(), - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "The file \"{}\" is not readable.", + self.file.borrow().get_path(), + )) .into()); } @@ -148,19 +143,16 @@ impl JsonConfigSource { { Ok(_) => {} Err(e) => { - let Some(jve) = e.downcast_ref::<JsonValidationException>() else { + let Some(jve) = e.catch::<JsonValidationException>() else { return Err(e); }; // restore contents to the original state file_put_contents(self.file.borrow().get_path(), contents.as_bytes()); - return Err(RuntimeException { - 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()), - ), - code: 0, - } + return Err(RuntimeException::new(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()), + )) .into()); } } @@ -397,13 +389,10 @@ impl ConfigSourceInterface for JsonConfigSource { } } let Some(index_to_insert) = index_to_insert else { - return Err(RuntimeException { - message: format!( - "The referenced repository \"{}\" does not exist.", - reference_name, - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "The referenced repository \"{}\" does not exist.", + reference_name, + )) .into()); }; diff --git a/crates/shirabe/src/console/application.rs b/crates/shirabe/src/console/application.rs index 96f80d9b..16a67b72 100644 --- a/crates/shirabe/src/console/application.rs +++ b/crates/shirabe/src/console/application.rs @@ -93,6 +93,7 @@ use shirabe_external_packages::symfony::console::style::style_interface::StyleIn use shirabe_external_packages::symfony::console::style::symfony_style::SymfonyStyle; use shirabe_external_packages::symfony::console::terminal::Terminal; use shirabe_external_packages::symfony::process::exception::ProcessTimedOutException; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ LogicException as ShimLogicException, PHP_VERSION, PHP_VERSION_ID, PhpMixed, RuntimeException, bin2hex, chdir, date_default_timezone_get, date_default_timezone_set, defined, dirname, @@ -232,13 +233,10 @@ impl Application { if let Some(ref wd) = working_dir && !is_dir(wd) { - return Err(RuntimeException { - message: format!( - "Invalid working directory specified, {} does not exist.", - wd - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Invalid working directory specified, {} does not exist.", + wd + )) .into()); } @@ -250,8 +248,8 @@ impl Application { exception: &anyhow::Error, output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, ) { - let is_logic_or_error = exception.downcast_ref::<ShimLogicException>().is_some(); - if is_logic_or_error + if (exception.is_class::<ShimLogicException>() + || exception.is_instanceof::<shirabe_php_shim::Error>()) && output.borrow().get_verbosity() < output_interface::VERBOSITY_VERBOSE { output @@ -308,7 +306,7 @@ impl Application { } let message = exception.to_string(); - if exception.downcast_ref::<TransportException>().is_some() + if exception.is_instanceof::<TransportException>() && str_contains(&message, "Unable to use a proxy") { io.write_error3( @@ -320,7 +318,7 @@ impl Application { } if Platform::is_windows() - && exception.downcast_ref::<TransportException>().is_some() + && exception.is_instanceof::<TransportException>() && str_contains(&message, "unable to get local issuer certificate") { let avast_detect = glob("C:\\Program Files\\Avast*"); @@ -359,10 +357,7 @@ impl Application { io.write_error3("<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details</error>", true, io_interface::QUIET); } - if exception - .downcast_ref::<ProcessTimedOutException>() - .is_some() - { + if exception.is_instanceof::<ProcessTimedOutException>() { io.write_error3( "<error>The following exception is caused by a process timeout</error>", true, @@ -376,9 +371,7 @@ impl Application { && !self.io.is_interactive() { io.write_error3("<error>Plugins have been disabled automatically as you are running as root, this may be the cause of the following exception. See also https://getcomposer.org/root</error>", true, io_interface::QUIET); - } else if exception - .downcast_ref::<CommandNotFoundException>() - .is_some() + } else if exception.is_instanceof::<CommandNotFoundException>() && self.get_disable_plugins_by_default() { io.write_error3("<error>Plugins have been disabled, which may be why some commands are missing, unless you made a typo</error>", true, io_interface::QUIET); @@ -416,9 +409,7 @@ impl Application { match Factory::create(io_for_factory, None, disable_plugins_enum, disable_scripts) { Ok(c) => self.composer = Some(c.upcast()), Err(e) => { - if e.downcast_ref::<shirabe_php_shim::InvalidArgumentException>() - .is_some() - { + if e.is_instanceof::<shirabe_php_shim::InvalidArgumentException>() { if required { self.io.write_error(&e.to_string()); if self.are_exceptions_caught() { @@ -429,11 +420,11 @@ impl Application { } return Err(e); } - } else if e.downcast_ref::<JsonValidationException>().is_some() - || e.downcast_ref::<RuntimeException>().is_some() + } else if e.is_instanceof::<JsonValidationException>() + || e.is_instanceof::<RuntimeException>() // PHP's `catch (RuntimeException)` also catches subclasses; // NoSslException is the one Factory::create raises. - || e.downcast_ref::<NoSslException>().is_some() + || e.is_instanceof::<NoSslException>() { if required { return Err(e); @@ -523,7 +514,7 @@ impl Application { Ok(()) => {} Err(e) => { // Errors must be ignored, full binding/validation happens later when the command is known. - if !is_exception_interface(&e) { + if !throwable_is_exception_interface(e.as_ref()) { return Err(e); } } @@ -712,10 +703,7 @@ impl Application { pub fn get_signal_registry(&self) -> anyhow::Result<&SignalRegistry> { match &self.signal_registry { - None => Err(ConsoleRuntimeException(shirabe_php_shim::RuntimeException { - message: "Signals are not supported. Make sure that the `pcntl` extension is installed and that \"pcntl_*\" functions are not disabled by your php.ini's \"disable_functions\" directive.".to_string(), - code: 0, - }) + None => Err(ConsoleRuntimeException::new("Signals are not supported. Make sure that the `pcntl` extension is installed and that \"pcntl_*\" functions are not disabled by your php.ini's \"disable_functions\" directive.".to_string()) .into()), Some(signal_registry) => Ok(signal_registry), } @@ -988,17 +976,12 @@ impl Application { message.push_str(&shirabe_php_shim::implode("\n ", &alternatives)); } - return Err(NamespaceNotFoundException(CommandNotFoundException::new( - message, - alternatives, - 0, - )) - .into()); + return Err(NamespaceNotFoundException::new(message, alternatives, 0).into()); } let exact = namespaces.iter().any(|n| n == namespace); if namespaces.len() > 1 && !exact { - return Err(NamespaceNotFoundException(CommandNotFoundException::new( + return Err(NamespaceNotFoundException::new( format!( "The namespace \"{}\" is ambiguous.\nDid you mean one of these?\n{}.", namespace, @@ -1006,7 +989,7 @@ impl Application { ), namespaces, 0, - )) + ) .into()); } @@ -1936,13 +1919,10 @@ impl ApplicationHandle { command.borrow().get_definition(); if command.borrow().get_name().is_none() { - return Err(ConsoleLogicException(shirabe_php_shim::LogicException { - message: format!( - "The command defined in \"{}\" cannot have an empty name.", - shirabe_php_shim::get_debug_type_obj(&command), - ), - code: 0, - }) + return Err(ConsoleLogicException::new(format!( + "The command defined in \"{}\" cannot have an empty name.", + shirabe_php_shim::get_debug_type_obj(&command), + )) .into()); } @@ -2057,7 +2037,7 @@ impl ApplicationHandle { command_name = cmd.borrow().get_name(); } Err(e) => { - if e.downcast_ref::<CommandNotFoundException>().is_some() { + if e.is_instanceof::<CommandNotFoundException>() { // we'll check command validity again later after plugins are loaded command_name = None; } @@ -2223,9 +2203,9 @@ impl ApplicationHandle { })() { Ok(_) => {} Err(e) => { - if e.downcast_ref::<NoSslException>().is_some() { + if e.is_instanceof::<NoSslException>() { // suppress these as they are not relevant at this point - } else if let Some(pe) = e.downcast_ref::<ParsingException>() { + } else if let Some(pe) = e.catch::<ParsingException>() { let details = pe.get_details(); let file = realpath(Factory::get_composer_file().unwrap_or_default()); @@ -2233,7 +2213,7 @@ impl ApplicationHandle { let line = details.line; let mut ghe = GithubActionError::new(io.clone()); - ghe.emit(&pe.message, file.as_deref(), line); + ghe.emit(pe.get_message(), file.as_deref(), line); return Err(e); } else { @@ -2542,7 +2522,7 @@ impl ApplicationHandle { let outcome = match result_outcome { Ok(r) => Ok(r), - Err(e) => { + Err(mut e) => { // PHP's `exit` bypasses parent::doRun()'s catch entirely; re-raise it untouched so // the GitHub Actions annotation and error hints below are skipped. if e.downcast_ref::<shirabe_php_shim::ExitException>() @@ -2550,7 +2530,7 @@ impl ApplicationHandle { { return Err(e); } - if let Some(see) = e.downcast_ref::<ScriptExecutionException>() { + if let Some(see) = e.catch::<ScriptExecutionException>() { if application.borrow().get_disable_plugins_by_default() && application.borrow().is_running_as_root() && !io.is_interactive() @@ -2572,15 +2552,9 @@ impl ApplicationHandle { // override TransportException's code for the purpose of parent::run() using it as process exit code // as http error codes are all beyond the 255 range of permitted exit codes - // TODO(phase-c): PHP's `instanceof TransportException` also matches the subclass - // MaxFileSizeExceededException, which is a newtype here and is not matched by this downcast. - let e = match e.downcast::<TransportException>() { - Ok(mut e) => { - e.code = Installer::ERROR_TRANSPORT_EXCEPTION; - anyhow::Error::new(e) - } - Err(e) => e, - }; + if let Some(te) = e.catch_mut::<TransportException>() { + te.set_code(Installer::ERROR_TRANSPORT_EXCEPTION); + } Err(e) } @@ -2691,9 +2665,7 @@ impl ApplicationHandle { // $exitCode = $e->getCode(); // is_numeric($exitCode) ? max(1, (int) $exitCode) : 1 - // TODO(phase-c): anyhow::Error has no PHP-style getCode(); the exit code derived - // from the exception's `code` field needs the downcast strategy decided. - let exit_code = shirabe_php_shim::php_exception_get_code(&e); + let exit_code = throwable_get_code(e.as_ref()) as i32; if shirabe_php_shim::is_numeric_string(&exit_code.to_string()) { if exit_code <= 0 { 1 } else { exit_code } } else { @@ -2735,7 +2707,7 @@ impl ApplicationHandle { Ok(()) => {} Err(e) => { // Errors must be ignored, full binding/validation happens later when the command is known. - if !is_exception_interface(&e) { + if !throwable_is_exception_interface(e.as_ref()) { return Err(e); } } @@ -2814,8 +2786,9 @@ impl ApplicationHandle { Err(e) => { // if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) // || 1 !== count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) - let alternatives: Option<Vec<String>> = downcast_command_not_found(&e) - .filter(|_| !is_namespace_not_found(&e)) + let alternatives: Option<Vec<String>> = e + .catch::<CommandNotFoundException>() + .filter(|_| !e.is_instanceof::<NamespaceNotFoundException>()) .map(|cnf| cnf.get_alternatives().clone()); let single_alternative = match &alternatives { @@ -2907,10 +2880,7 @@ impl ApplicationHandle { if !command_signals.is_empty() { if application.borrow().signal_registry.is_none() { - return Err(ConsoleRuntimeException(shirabe_php_shim::RuntimeException { - message: "Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that \"pcntl_*\" functions are not disabled by your php.ini's \"disable_functions\" directive.".to_string(), - code: 0, - }) + return Err(ConsoleRuntimeException::new("Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that \"pcntl_*\" functions are not disabled by your php.ini's \"disable_functions\" directive.".to_string()) .into()); } @@ -2994,133 +2964,35 @@ impl BaseApplication for Application { } } -/// Helper mirroring PHP's `$e instanceof ExceptionInterface`. -fn is_exception_interface(e: &anyhow::Error) -> bool { - // anyhow::Error stores concrete error types; enumerate the console exceptions - // that implement ExceptionInterface (PHP's `$e instanceof ExceptionInterface`). - e.downcast_ref::<CommandNotFoundException>().is_some() - || e.downcast_ref::<NamespaceNotFoundException>().is_some() - || e.downcast_ref::<ConsoleLogicException>().is_some() - || e.downcast_ref::<ConsoleRuntimeException>().is_some() - || e.downcast_ref::<ConsoleInvalidArgumentException>() - .is_some() - || e.downcast_ref::<InvalidOptionException>().is_some() - || e.downcast_ref::<MissingInputException>().is_some() -} - -/// `is_exception_interface` for a node of the `anyhow::Error` source chain (`&dyn Error`), used -/// while walking the getPrevious() chain in `do_render_throwable`. +/// PHP's `$e instanceof ExceptionInterface`, enumerating the console exceptions that implement it. +// TODO(plugin): a plugin can throw an exception class of its own that implements +// ExceptionInterface, and no enumeration on this side can name it. Answering that needs the +// interfaces a thrown exception implements, not just its superclasses. fn throwable_is_exception_interface(e: &(dyn std::error::Error + 'static)) -> bool { - e.downcast_ref::<CommandNotFoundException>().is_some() - || e.downcast_ref::<NamespaceNotFoundException>().is_some() - || e.downcast_ref::<ConsoleLogicException>().is_some() - || e.downcast_ref::<ConsoleRuntimeException>().is_some() - || e.downcast_ref::<ConsoleInvalidArgumentException>() - .is_some() - || e.downcast_ref::<InvalidOptionException>().is_some() - || e.downcast_ref::<MissingInputException>().is_some() + shirabe_php_shim::AnyThrowable::of(e).is_some_and(|e| { + e.is_instanceof::<CommandNotFoundException>() + || e.is_instanceof::<NamespaceNotFoundException>() + || e.is_instanceof::<ConsoleLogicException>() + || e.is_instanceof::<ConsoleRuntimeException>() + || e.is_instanceof::<ConsoleInvalidArgumentException>() + || e.is_instanceof::<InvalidOptionException>() + || e.is_instanceof::<MissingInputException>() + }) } -/// PHP's `$e->getCode()` for a node of the source chain. Enumerates the flat standard exception -/// structs that carry a `code`; everything else defaults to PHP's 0. +/// PHP's `$e->getCode()` for a node of the source chain; everything the port does not recognize as +/// an exception defaults to PHP's 0. fn throwable_get_code(e: &(dyn std::error::Error + 'static)) -> i64 { - if let Some(e) = e.downcast_ref::<shirabe_php_shim::Exception>() { - return e.code; - } - if let Some(e) = e.downcast_ref::<shirabe_php_shim::RuntimeException>() { - return e.code; - } - if let Some(e) = e.downcast_ref::<shirabe_php_shim::UnexpectedValueException>() { - return e.code; - } - if let Some(e) = e.downcast_ref::<shirabe_php_shim::InvalidArgumentException>() { - return e.code; - } - if let Some(e) = e.downcast_ref::<shirabe_php_shim::TypeError>() { - return e.code; - } - if let Some(e) = e.downcast_ref::<shirabe_php_shim::LogicException>() { - return e.code; - } - if let Some(e) = e.downcast_ref::<shirabe_php_shim::BadMethodCallException>() { - return e.code; - } - if let Some(e) = e.downcast_ref::<shirabe_php_shim::OutOfBoundsException>() { - return e.code; - } - if let Some(e) = e.downcast_ref::<shirabe_php_shim::ErrorException>() { - return e.code; - } - if let Some(e) = e.downcast_ref::<shirabe_php_shim::PharException>() { - return e.code; - } - 0 + shirabe_php_shim::AnyThrowable::of(e).map_or(0, shirabe_php_shim::AnyThrowable::get_code) } /// PHP's `get_debug_type($e)` for the title line, reached only when the message is empty or output -/// is verbose. PHP returns the exception's fully-qualified class name; Rust has no runtime FQCN, so -/// this maps the enumerable exception types to their PHP class names and falls back to `Exception`. -/// TODO(phase-c): the fully-qualified name (e.g. `Composer\...`) cannot be reproduced faithfully. +/// is verbose. fn throwable_debug_type(e: &(dyn std::error::Error + 'static)) -> String { - let name = if e - .downcast_ref::<shirabe_php_shim::RuntimeException>() - .is_some() - { - "RuntimeException" - } else if e - .downcast_ref::<shirabe_php_shim::UnexpectedValueException>() - .is_some() - { - "UnexpectedValueException" - } else if e - .downcast_ref::<shirabe_php_shim::InvalidArgumentException>() - .is_some() - { - "InvalidArgumentException" - } else if e.downcast_ref::<shirabe_php_shim::TypeError>().is_some() { - "TypeError" - } else if e - .downcast_ref::<shirabe_php_shim::LogicException>() - .is_some() - { - "LogicException" - } else if e - .downcast_ref::<shirabe_php_shim::BadMethodCallException>() - .is_some() - { - "BadMethodCallException" - } else if e - .downcast_ref::<shirabe_php_shim::OutOfBoundsException>() - .is_some() - { - "OutOfBoundsException" - } else if e - .downcast_ref::<shirabe_php_shim::ErrorException>() - .is_some() - { - "ErrorException" - } else if e - .downcast_ref::<shirabe_php_shim::PharException>() - .is_some() - { - "PharException" - } else { - "Exception" - }; - name.to_string() -} - -/// Helper mirroring PHP's `$e instanceof CommandNotFoundException`. -fn downcast_command_not_found(e: &anyhow::Error) -> Option<&CommandNotFoundException> { - if let Some(cnf) = e.downcast_ref::<CommandNotFoundException>() { - return Some(cnf); - } - e.downcast_ref::<NamespaceNotFoundException>().map(|n| &n.0) -} - -/// Helper mirroring PHP's `$e instanceof NamespaceNotFoundException`. -fn is_namespace_not_found(e: &anyhow::Error) -> bool { - e.downcast_ref::<NamespaceNotFoundException>().is_some() + shirabe_php_shim::AnyThrowable::of(e).map_or_else( + || "Exception".to_string(), + shirabe_php_shim::PhpClass::php_class_name, + ) } /// Borrows the shared input as a mutable `dyn InputInterface` for passing to @@ -3161,13 +3033,11 @@ pub(crate) fn register_worker_reverse_application( pub(crate) fn run_worker_reverse_command(name: &str, input_line: &str) -> anyhow::Result<i64> { let application = WORKER_REVERSE_APPLICATION .with(|slot| slot.borrow().as_ref().and_then(std::rc::Weak::upgrade)) - .ok_or_else(|| { - anyhow::anyhow!(shirabe_php_shim::RuntimeException { - message: format!( - "cannot run command {name}: no application is registered for worker callbacks" - ), - code: 0, - }) + .ok_or_else(|| -> anyhow::Error { + shirabe_php_shim::RuntimeException::new(format!( + "cannot run command {name}: no application is registered for worker callbacks" + )) + .into() })?; let command = application.borrow_mut().find(name)?; // PHP's `(string) $input` omits the command name when the input was built without an diff --git a/crates/shirabe/src/dependency_resolver/decisions.rs b/crates/shirabe/src/dependency_resolver/decisions.rs index 81f00884..ac596054 100644 --- a/crates/shirabe/src/dependency_resolver/decisions.rs +++ b/crates/shirabe/src/dependency_resolver/decisions.rs @@ -102,14 +102,11 @@ impl Decisions { panic!( "{}", - LogicException { - message: format!( - "Did not find a decision rule using {}", - literal_or_package_id - ), - code: 0, - } - .message + LogicException::new(format!( + "Did not find a decision rule using {}", + literal_or_package_id + )) + .get_message() ); } @@ -171,7 +168,7 @@ impl Decisions { literal_string, level, package, previous_decision )) .0 - .message + .get_message() ); } diff --git a/crates/shirabe/src/dependency_resolver/generic_rule.rs b/crates/shirabe/src/dependency_resolver/generic_rule.rs index 8b64dc46..3f098f01 100644 --- a/crates/shirabe/src/dependency_resolver/generic_rule.rs +++ b/crates/shirabe/src/dependency_resolver/generic_rule.rs @@ -44,11 +44,7 @@ impl GenericRule { let binary = hash_raw(algo, &joined); match binary.get(..4) { Some(chunk) => Ok(i32::from_ne_bytes(chunk.try_into().unwrap()) as i64), - None => Err(RuntimeException { - message: format!("Failed unpacking: {}", joined), - code: 0, - } - .into()), + None => Err(RuntimeException::new(format!("Failed unpacking: {}", joined)).into()), } } diff --git a/crates/shirabe/src/dependency_resolver/multi_conflict_rule.rs b/crates/shirabe/src/dependency_resolver/multi_conflict_rule.rs index 0936dd0c..d43d9a79 100644 --- a/crates/shirabe/src/dependency_resolver/multi_conflict_rule.rs +++ b/crates/shirabe/src/dependency_resolver/multi_conflict_rule.rs @@ -16,10 +16,9 @@ impl MultiConflictRule { reason_data: ReasonData, ) -> anyhow::Result<Self> { if literals.len() < 3 { - return Err(RuntimeException { - message: "multi conflict rule requires at least 3 literals".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "multi conflict rule requires at least 3 literals".to_string(), + ) .into()); } @@ -59,11 +58,7 @@ impl MultiConflictRule { let binary = hash_raw(algo, &format!("c:{}", joined)); match binary.get(..4) { Some(chunk) => Ok(i32::from_ne_bytes(chunk.try_into().unwrap()) as i64), - None => Err(RuntimeException { - message: format!("Failed unpacking: {}", joined), - code: 0, - } - .into()), + None => Err(RuntimeException::new(format!("Failed unpacking: {}", joined)).into()), } } diff --git a/crates/shirabe/src/dependency_resolver/pool_builder.rs b/crates/shirabe/src/dependency_resolver/pool_builder.rs index 35699a90..f5143897 100644 --- a/crates/shirabe/src/dependency_resolver/pool_builder.rs +++ b/crates/shirabe/src/dependency_resolver/pool_builder.rs @@ -147,11 +147,9 @@ impl PoolBuilder { self.warn_about_non_matching_update_allow_list(request)?; if request.get_locked_repository().is_none() { - return Err(LogicException { - message: "No lock repo present and yet a partial update was requested." - .to_string(), - code: 0, - } + return Err(LogicException::new( + "No lock repo present and yet a partial update was requested.".to_string(), + ) .into()); } @@ -798,10 +796,9 @@ impl PoolBuilder { fn warn_about_non_matching_update_allow_list(&self, request: &Request) -> anyhow::Result<()> { if request.get_locked_repository().is_none() { - return Err(LogicException { - message: "No lock repo present and yet a partial update was requested.".to_string(), - code: 0, - } + return Err(LogicException::new( + "No lock repo present and yet a partial update was requested.".to_string(), + ) .into()); } diff --git a/crates/shirabe/src/dependency_resolver/problem.rs b/crates/shirabe/src/dependency_resolver/problem.rs index 3c69babe..3b9feba5 100644 --- a/crates/shirabe/src/dependency_resolver/problem.rs +++ b/crates/shirabe/src/dependency_resolver/problem.rs @@ -82,10 +82,9 @@ impl Problem { let rule_ref = rule.borrow(); if rule_ref.get_reason() != rule::RULE_ROOT_REQUIRE { - return Err(LogicException { - message: "Single reason problems must contain a root require rule.".to_string(), - code: 0, - } + return Err(LogicException::new( + "Single reason problems must contain a root require rule.".to_string(), + ) .into()); } diff --git a/crates/shirabe/src/dependency_resolver/request.rs b/crates/shirabe/src/dependency_resolver/request.rs index d78900c0..410831dd 100644 --- a/crates/shirabe/src/dependency_resolver/request.rs +++ b/crates/shirabe/src/dependency_resolver/request.rs @@ -47,15 +47,12 @@ impl Request { let package_name = strtolower(package_name); let constraint = constraint.unwrap_or_else(|| MatchAllConstraint::new(None).into()); if self.requires.contains_key(&package_name) { - return Err(LogicException { - message: format!( - "Overwriting requires seems like a bug ({} {} => {}, check why it is happening, might be a root alias", - package_name, - self.requires[&package_name].get_pretty_string(), - constraint.get_pretty_string() - ), - code: 0, - } + return Err(LogicException::new(format!( + "Overwriting requires seems like a bug ({} {} => {}, check why it is happening, might be a root alias", + package_name, + self.requires[&package_name].get_pretty_string(), + constraint.get_pretty_string() + )) .into()); } self.requires.insert(package_name, constraint); diff --git a/crates/shirabe/src/dependency_resolver/rule.rs b/crates/shirabe/src/dependency_resolver/rule.rs index 0ed442ce..5e009260 100644 --- a/crates/shirabe/src/dependency_resolver/rule.rs +++ b/crates/shirabe/src/dependency_resolver/rule.rs @@ -166,10 +166,7 @@ impl Rule { pub fn disable(&mut self) -> anyhow::Result<()> { if let Rule::MultiConflict(_) = self { - return Err(RuntimeException { - message: "Disabling multi conflict rules is not possible. Please contact composer at https://github.com/composer/composer to let us debug what lead to this situation.".to_string(), - code: 0, - } + return Err(RuntimeException::new("Disabling multi conflict rules is not possible. Please contact composer at https://github.com/composer/composer to let us debug what lead to this situation.".to_string()) .into()); } *self.bitfield_mut() = @@ -291,11 +288,7 @@ impl Rule { Ok(source_package) } - _ => Err(LogicException { - message: "Not implemented".to_string(), - code: 0, - } - .into()), + _ => Err(LogicException::new("Not implemented".to_string()).into()), } } diff --git a/crates/shirabe/src/dependency_resolver/rule_set.rs b/crates/shirabe/src/dependency_resolver/rule_set.rs index f7300d93..50f7cb45 100644 --- a/crates/shirabe/src/dependency_resolver/rule_set.rs +++ b/crates/shirabe/src/dependency_resolver/rule_set.rs @@ -59,11 +59,7 @@ impl RuleSet { ) -> anyhow::Result<()> { let types = Self::types(); if !types.contains_key(&r#type) { - return Err(OutOfBoundsException { - message: format!("Unknown rule type: {}", r#type), - code: 0, - } - .into()); + return Err(OutOfBoundsException::new(format!("Unknown rule type: {}", r#type)).into()); } let hash = rule.borrow().get_hash()?.to_string(); diff --git a/crates/shirabe/src/dependency_resolver/rule_set_generator.rs b/crates/shirabe/src/dependency_resolver/rule_set_generator.rs index 2f4837d4..6f2213dc 100644 --- a/crates/shirabe/src/dependency_resolver/rule_set_generator.rs +++ b/crates/shirabe/src/dependency_resolver/rule_set_generator.rs @@ -309,13 +309,11 @@ impl RuleSetGenerator { } // otherwise, looks like a bug - return Err(anyhow::anyhow!(shirabe_php_shim::LogicException { - message: format!( - "Fixed package {} was not added to solver pool.", - package.get_pretty_string() - ), - code: 0, - })); + return Err(shirabe_php_shim::LogicException::new(format!( + "Fixed package {} was not added to solver pool.", + package.get_pretty_string() + )) + .into()); } self.add_rules_for_package(package.clone(), platform_requirement_filter); diff --git a/crates/shirabe/src/dependency_resolver/solver.rs b/crates/shirabe/src/dependency_resolver/solver.rs index dd5bd383..a9453a3e 100644 --- a/crates/shirabe/src/dependency_resolver/solver.rs +++ b/crates/shirabe/src/dependency_resolver/solver.rs @@ -381,10 +381,11 @@ impl Solver { let (learn_literal, new_level, new_rule, why) = self.analyze(level, rule)?; if new_level <= 0 || new_level >= level { - return Err(anyhow::anyhow!(SolverBugException::new(format!( + return Err(SolverBugException::new(format!( "Trying to revert to invalid level {} from level {}.", new_level, level - )))); + )) + .into()); } level = new_level; @@ -505,12 +506,12 @@ impl Solver { let inner_literal = loop { if decision_id <= 0 { - return Err(anyhow::anyhow!(SolverBugException::new(format!( + return Err(SolverBugException::new(format!( "Reached invalid decision id {} while looking through {} for a literal present in the analyzed rule {}.", decision_id, rule.borrow(), analyzed_rule.borrow() - )))); + )).into()); } decision_id -= 1; @@ -587,10 +588,11 @@ impl Solver { let learned_literal = match learned_literal { Some(l) => l, None => { - return Err(anyhow::anyhow!(SolverBugException::new(format!( + return Err(SolverBugException::new(format!( "Did not find a learnable literal in analyzed rule {}.", analyzed_rule.borrow() - )))); + )) + .into()); } }; diff --git a/crates/shirabe/src/dependency_resolver/solver_bug_exception.rs b/crates/shirabe/src/dependency_resolver/solver_bug_exception.rs index d509ef6a..9c47d99b 100644 --- a/crates/shirabe/src/dependency_resolver/solver_bug_exception.rs +++ b/crates/shirabe/src/dependency_resolver/solver_bug_exception.rs @@ -12,17 +12,12 @@ impl SolverBugException { Please report the command you ran, the exact error you received, and your composer.json on https://github.com/composer/composer/issues - thank you!\n", message ); - SolverBugException(RuntimeException { - message: full_message, - code: 0, - }) + SolverBugException(RuntimeException::new(full_message)) } } -impl std::fmt::Display for SolverBugException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl std::error::Error for SolverBugException {} +shirabe_php_shim::impl_php_exception!( + SolverBugException, + 0, + r"Composer\DependencyResolver\SolverBugException" +); diff --git a/crates/shirabe/src/dependency_resolver/solver_problems_exception.rs b/crates/shirabe/src/dependency_resolver/solver_problems_exception.rs index 39f2b2c1..4c948608 100644 --- a/crates/shirabe/src/dependency_resolver/solver_problems_exception.rs +++ b/crates/shirabe/src/dependency_resolver/solver_problems_exception.rs @@ -18,14 +18,6 @@ pub struct SolverProblemsException { impl SolverProblemsException { pub const ERROR_DEPENDENCY_RESOLUTION_FAILED: i64 = 2; - pub fn get_code(&self) -> i64 { - self.inner.code - } - - pub fn get_message(&self) -> &str { - &self.inner.message - } - pub fn new( problems: Vec<Problem>, learned_pool: Vec<Vec<std::rc::Rc<std::cell::RefCell<Rule>>>>, @@ -35,10 +27,7 @@ impl SolverProblemsException { problems.len() ); Self { - inner: RuntimeException { - message, - code: Self::ERROR_DEPENDENCY_RESOLUTION_FAILED, - }, + inner: RuntimeException::with_code(message, Self::ERROR_DEPENDENCY_RESOLUTION_FAILED), problems, learned_pool, } @@ -170,10 +159,11 @@ impl SolverProblemsException { } } -impl std::fmt::Display for SolverProblemsException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.inner.message) - } -} - -impl std::error::Error for SolverProblemsException {} +// `learned_pool` holds `Rc<RefCell<Rule>>`, so this exception rides an inner `Result` rather than +// an `anyhow::Error`. +shirabe_php_shim::impl_php_exception!( + SolverProblemsException, + inner, + r"Composer\DependencyResolver\SolverProblemsException", + !Send +); 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()); } { diff --git a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs index b821f647..3db924d4 100644 --- a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs +++ b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs @@ -29,6 +29,7 @@ use shirabe_php_rpc::{ PhpThrow, PluginValue, RustMethodDispatcher, RustObjHandle, call_function, call_function_with_dispatcher, call_php_method, call_static_method, }; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ InvalidArgumentException, PATH_SEPARATOR, PhpMixed, RuntimeException, array_pop, array_push, array_search_in_vec, array_splice, file_exists, get_class, hash, implode, ini_get, is_array, @@ -201,13 +202,9 @@ impl EventDispatcher { ) -> anyhow::Result<i64> { match event { None => { - let name = event_name.ok_or_else(|| { - anyhow::anyhow!(InvalidArgumentException { - message: - "If no $event is passed in to Composer\\EventDispatcher\\EventDispatcher::dispatch you have to pass in an $eventName, got null." - .to_string(), - code: 0, - }) + let name = event_name.ok_or_else(|| -> anyhow::Error { + InvalidArgumentException::new("If no $event is passed in to Composer\\EventDispatcher\\EventDispatcher::dispatch you have to pass in an $eventName, got null." + .to_string()).into() })?; let mut event = Event::new(name.to_string(), Vec::new(), IndexMap::new()); self.do_dispatch(&mut event) @@ -411,15 +408,12 @@ impl EventDispatcher { Some(&mut PluginRpcDispatcher::default()), ))?; if !matches!(is_callable_value, PluginValue::Bool(true)) { - return Err(anyhow::anyhow!(RuntimeException { - message: format!( - "Subscriber {}::{} for event {} is not callable, make sure the function is defined and public", - handle.class, - method_name, - event.get_name() - ), - code: 0, - })); + return Err(RuntimeException::new(format!( + "Subscriber {}::{} for event {} is not callable, make sure the function is defined and public", + handle.class, + method_name, + event.get_name() + )).into()); } self.io.write_error3( &format!( @@ -431,18 +425,17 @@ impl EventDispatcher { true, crate::io::VERBOSE, ); - let stub_class = Self::event_stub_class(event).ok_or_else(|| { - // TODO(plugin): installer and plugin events have no proxy stub yet. - anyhow::anyhow!(RuntimeException { - message: format!( - "no proxy stub is available yet for the event `{}` dispatched to {}::{}", - event.get_name(), - handle.class, - method_name, - ), - code: 0, - }) - })?; + let stub_class = + Self::event_stub_class(event).ok_or_else(|| -> anyhow::Error { + // TODO(plugin): installer and plugin events have no proxy stub yet. + RuntimeException::new(format!( + "no proxy stub is available yet for the event `{}` dispatched to {}::{}", + event.get_name(), + handle.class, + method_name, + )) + .into() + })?; let event_rhandle = shirabe_php_rpc::alloc_rhandle(); let mut dispatcher = PluginRpcDispatcher { event: Some((event_rhandle, event)), @@ -469,10 +462,7 @@ impl EventDispatcher { // TODO(plugin): the original exception class is collapsed to // RuntimeException on this side of the boundary. Err(throw) => { - return Err(anyhow::anyhow!(RuntimeException { - message: throw.message, - code: throw.code, - })); + return Err(RuntimeException::with_code(throw.message, throw.code).into()); } }; } else if !is_string_callable { @@ -494,15 +484,12 @@ impl EventDispatcher { } _ => ("?".to_string(), "?".to_string()), }; - return Err(anyhow::anyhow!(RuntimeException { - message: format!( - "Subscriber {}::{} for event {} is not callable, make sure the function is defined and public", - class_name, - method, - event.get_name() - ), - code: 0, - })); + return Err(RuntimeException::new(format!( + "Subscriber {}::{} for event {} is not callable, make sure the function is defined and public", + class_name, + method, + event.get_name() + )).into()); } if let Callable::ArrayCallable(first, method_name) = &callable { let prefix = if is_object(first.as_ref()) { @@ -594,15 +581,14 @@ impl EventDispatcher { exit_code ), true, crate::io::QUIET); - return Err(anyhow::anyhow!(ScriptExecutionException( - RuntimeException { - message: format!( - "Error Output: {}", - self.process.borrow().get_error_output() - ), - code: exit_code, - } - ))); + return Err(ScriptExecutionException::new( + format!( + "Error Output: {}", + self.process.borrow().get_error_output() + ), + exit_code, + ) + .into()); } } else { if self @@ -638,7 +624,7 @@ impl EventDispatcher { match self.dispatch(Some(&script_name), Some(&mut script_event)) { Ok(v) => r#return = v, Err(e) => { - if e.downcast_ref::<ScriptExecutionException>().is_some() { + if e.is_instanceof::<ScriptExecutionException>() { self.io.write_error3( &format!( "<error>Script {} was called via {}</error>", @@ -839,10 +825,9 @@ try {{ true, crate::io::QUIET, ); - return Err(anyhow::anyhow!(RuntimeException { - message: throw.message, - code: throw.code, - })); + return Err( + RuntimeException::with_code(throw.message, throw.code).into() + ); } }; let command_output = result @@ -876,7 +861,7 @@ try {{ true, crate::io::QUIET, ); - return Err(anyhow::anyhow!(RuntimeException { message, code })); + return Err(RuntimeException::with_code(message, code).into()); } r#return = match result.as_array().and_then(|map| map.get("status")) { Some(PhpMixed::Int(status)) => *status, @@ -1043,15 +1028,14 @@ try {{ exit_code ), true, crate::io::QUIET); - return Err(anyhow::anyhow!(ScriptExecutionException( - RuntimeException { - message: format!( - "Error Output: {}", - self.process.borrow().get_error_output() - ), - code: exit_code, - } - ))); + return Err(ScriptExecutionException::new( + format!( + "Error Output: {}", + self.process.borrow().get_error_output() + ), + exit_code, + ) + .into()); } } _ => { @@ -1097,10 +1081,10 @@ try {{ let php_path = match php_path { Some(p) => p, None => { - return Err(anyhow::anyhow!(RuntimeException { - message: "Failed to locate PHP binary to execute ".to_string(), - code: 0, - })); + return Err(RuntimeException::new( + "Failed to locate PHP binary to execute ".to_string(), + ) + .into()); } }; let php_args = finder.find_arguments(); @@ -1152,17 +1136,15 @@ try {{ ); } - let stub_class = Self::event_stub_class(event).ok_or_else(|| { + let stub_class = Self::event_stub_class(event).ok_or_else(|| -> anyhow::Error { // TODO(plugin): installer and plugin events have no proxy stub yet. - anyhow::anyhow!(RuntimeException { - message: format!( - "no proxy stub is available yet for the event `{}` dispatched to {}::{}", - event.get_name(), - class_name, - method_name, - ), - code: 0, - }) + RuntimeException::new(format!( + "no proxy stub is available yet for the event `{}` dispatched to {}::{}", + event.get_name(), + class_name, + method_name, + )) + .into() })?; Self::ensure_script_autoloader()?; @@ -1186,10 +1168,7 @@ try {{ Ok(value) => Ok(value.to_php_mixed()?), // TODO(plugin): the original exception class is collapsed to RuntimeException on // this side of the boundary. - Err(throw) => Err(anyhow::anyhow!(RuntimeException { - message: throw.message, - code: throw.code, - })), + Err(throw) => Err(RuntimeException::with_code(throw.message, throw.code).into()), } } @@ -1395,13 +1374,11 @@ try {{ fn push_event(&mut self, event: &dyn EventInterface) -> anyhow::Result<i64> { let event_name = event.get_name().to_string(); if self.event_stack.iter().any(|n| n == &event_name) { - return Err(anyhow::anyhow!(RuntimeException { - message: format!( - "Circular call to script handler '{}' detected", - PhpMixed::String(event_name), - ), - code: 0, - })); + return Err(RuntimeException::new(format!( + "Circular call to script handler '{}' detected", + PhpMixed::String(event_name), + )) + .into()); } Ok(array_push(&mut self.event_stack, event_name)) @@ -1597,13 +1574,13 @@ try {{ pub(crate) fn ensure_composer_php_runtime() -> anyhow::Result<()> { // TODO(plugin): the real PHP classes are taken from a Composer checkout for now; how // they ship with a released Shirabe binary is part of the plugin distribution work. - let autoload = Self::composer_php_runtime_autoload().ok_or_else(|| { - anyhow::anyhow!(RuntimeException { - message: "unable to locate the Composer PHP runtime; set SHIRABE_COMPOSER_PHP_DIR \ + let autoload = Self::composer_php_runtime_autoload().ok_or_else(|| -> anyhow::Error { + RuntimeException::new( + "unable to locate the Composer PHP runtime; set SHIRABE_COMPOSER_PHP_DIR \ to a Composer checkout with its vendor directory installed" .to_string(), - code: 0, - }) + ) + .into() })?; unwrap_php_result(call_function( "__shirabe_require", @@ -1857,10 +1834,7 @@ pub(crate) fn unwrap_php_result( ) -> anyhow::Result<PluginValue> { match outcome? { Ok(value) => Ok(value), - Err(throw) => Err(anyhow::anyhow!(RuntimeException { - message: throw.message, - code: throw.code, - })), + Err(throw) => Err(RuntimeException::with_code(throw.message, throw.code).into()), } } diff --git a/crates/shirabe/src/event_dispatcher/script_execution_exception.rs b/crates/shirabe/src/event_dispatcher/script_execution_exception.rs index 05567b01..e4ce1ec0 100644 --- a/crates/shirabe/src/event_dispatcher/script_execution_exception.rs +++ b/crates/shirabe/src/event_dispatcher/script_execution_exception.rs @@ -7,19 +7,13 @@ use shirabe_php_shim::RuntimeException; pub struct ScriptExecutionException(pub RuntimeException); impl ScriptExecutionException { - pub fn get_code(&self) -> i64 { - self.0.code - } - - pub fn get_message(&self) -> &str { - &self.0.message - } -} - -impl std::fmt::Display for ScriptExecutionException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) + pub fn new(message: String, code: i64) -> Self { + Self(RuntimeException::with_code(message, code)) } } -impl std::error::Error for ScriptExecutionException {} +shirabe_php_shim::impl_php_exception!( + ScriptExecutionException, + 0, + r"Composer\EventDispatcher\ScriptExecutionException" +); diff --git a/crates/shirabe/src/exception/irrecoverable_download_exception.rs b/crates/shirabe/src/exception/irrecoverable_download_exception.rs index a8d2dbb4..ed424eff 100644 --- a/crates/shirabe/src/exception/irrecoverable_download_exception.rs +++ b/crates/shirabe/src/exception/irrecoverable_download_exception.rs @@ -5,10 +5,14 @@ use shirabe_php_shim::RuntimeException; #[derive(Debug)] pub struct IrrecoverableDownloadException(pub RuntimeException); -impl std::fmt::Display for IrrecoverableDownloadException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) +impl IrrecoverableDownloadException { + pub fn new(message: String) -> Self { + Self(RuntimeException::new(message)) } } -impl std::error::Error for IrrecoverableDownloadException {} +shirabe_php_shim::impl_php_exception!( + IrrecoverableDownloadException, + 0, + r"Composer\Exception\IrrecoverableDownloadException" +); diff --git a/crates/shirabe/src/exception/no_ssl_exception.rs b/crates/shirabe/src/exception/no_ssl_exception.rs index 968ecfd7..b0470e58 100644 --- a/crates/shirabe/src/exception/no_ssl_exception.rs +++ b/crates/shirabe/src/exception/no_ssl_exception.rs @@ -6,10 +6,10 @@ use shirabe_php_shim::RuntimeException; #[derive(Debug)] pub struct NoSslException(pub RuntimeException); -impl std::fmt::Display for NoSslException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) +impl NoSslException { + pub fn new(message: String) -> Self { + Self(RuntimeException::new(message)) } } -impl std::error::Error for NoSslException {} +shirabe_php_shim::impl_php_exception!(NoSslException, 0, r"Composer\Exception\NoSslException"); diff --git a/crates/shirabe/src/factory.rs b/crates/shirabe/src/factory.rs index 7328bf42..d7439d58 100644 --- a/crates/shirabe/src/factory.rs +++ b/crates/shirabe/src/factory.rs @@ -55,6 +55,7 @@ use shirabe_external_packages::symfony::console::formatter::OutputFormatter; use shirabe_external_packages::symfony::console::formatter::OutputFormatterStyle; use shirabe_external_packages::symfony::console::formatter::OutputFormatterStyleInterface; use shirabe_external_packages::symfony::console::output::ConsoleOutput; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ InvalidArgumentException, PATHINFO_EXTENSION, PHP_EOL, PHP_OS, PhpMixed, RuntimeException, UnexpectedValueException, array_replace_recursive, class_exists, dirname, extension_loaded, @@ -105,12 +106,8 @@ impl Factory { .map(|s| s.is_empty()) .unwrap_or(true) { - return Err(anyhow::anyhow!(RuntimeException { - message: - "The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly" - .to_string(), - code: 0, - })); + return Err(RuntimeException::new("The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly" + .to_string()).into()); } let appdata = Platform::get_env("APPDATA").unwrap_or_default(); @@ -358,13 +355,10 @@ impl Factory { let env_trimmed = trim(&env_str, Some(" \t\n\r\0\u{0B}")); if !env_trimmed.is_empty() { if is_dir(&env_trimmed) { - return Err(anyhow::anyhow!(RuntimeException { - message: format!( - "The COMPOSER environment variable is set to {} which is a directory, this variable should point to a composer.json or be left unset.", - env_trimmed - ), - code: 0, - })); + return Err(RuntimeException::new(format!( + "The COMPOSER environment variable is set to {} which is a directory, this variable should point to a composer.json or be left unset.", + env_trimmed + )).into()); } return Ok(env_trimmed); @@ -465,25 +459,25 @@ impl Factory { } else { "" }; - return Err(anyhow::anyhow!(InvalidArgumentException { - message: format!("{}{}{}", message, PHP_EOL, instructions), - code: 0, - })); + return Err(InvalidArgumentException::new(format!( + "{}{}{}", + message, PHP_EOL, instructions + )) + .into()); } if !Platform::is_input_completion_process() && let Err(e) = file.validate_schema(JsonFile::LAX_SCHEMA, None) { - if let Some(jve) = e.downcast_ref::<JsonValidationException>() { + if let Some(jve) = e.catch::<JsonValidationException>() { let errors = format!( " - {}", implode(&format!("{} - ", PHP_EOL), jve.get_errors()) ); let message = format!("{}:{}{}", jve.get_message(), PHP_EOL, errors); - return Err(anyhow::anyhow!(JsonValidationException::new( - message, - jve.get_errors().clone(), - ))); + return Err( + JsonValidationException::new(message, jve.get_errors().clone()).into(), + ); } return Err(e); } @@ -1358,10 +1352,7 @@ impl Factory { factory.create_composer(io, config, disable_plugins, None, true, disable_scripts)?; // fullLoad=true guarantees a full Composer; narrow PartialComposer -> Composer (PHP `: Composer`). composer.as_full().ok_or_else(|| { - anyhow::anyhow!(RuntimeException { - message: "Composer expected with fullLoad=true".to_string(), - code: 0, - }) + RuntimeException::new("Composer expected with fullLoad=true".to_string()).into() }) } @@ -1394,10 +1385,7 @@ impl Factory { let composer = factory.create_composer(io, config, disable_plugins, None, true, disable_scripts)?; composer.as_full().ok_or_else(|| { - anyhow::anyhow!(RuntimeException { - message: "Composer expected with fullLoad=true".to_string(), - code: 0, - }) + RuntimeException::new("Composer expected with fullLoad=true".to_string()).into() }) } @@ -1465,12 +1453,8 @@ impl Factory { unsafe { WARNED = true }; disable_tls = true; } else if !extension_loaded("openssl") { - return Err(anyhow::anyhow!(NoSslException(RuntimeException { - message: - "The openssl extension is required for SSL/TLS protection but is not available. If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the 'disable-tls' option to true." - .to_string(), - code: 0, - }))); + return Err(NoSslException::new("The openssl extension is required for SSL/TLS protection but is not available. If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the 'disable-tls' option to true." + .to_string()).into()); } let mut http_downloader_options: IndexMap<String, PhpMixed> = IndexMap::new(); if !disable_tls { @@ -1509,7 +1493,7 @@ impl Factory { let http_downloader = match http_downloader_result { Ok(h) => h, Err(e) => { - if let Some(te) = e.downcast_ref::<TransportException>() + if let Some(te) = e.catch::<TransportException>() && strpos(te.get_message(), "cafile").is_some() { io.write3( @@ -1547,12 +1531,11 @@ impl Factory { let auth_data = json_decode(&composer_auth_env_str, false)?; if matches!(auth_data, PhpMixed::Null) { - return Err(anyhow::anyhow!(UnexpectedValueException { - message: - "COMPOSER_AUTH environment variable is malformed, should be a valid JSON object" - .to_string(), - code: 0, - })); + return Err(UnexpectedValueException::new( + "COMPOSER_AUTH environment variable is malformed, should be a valid JSON object" + .to_string(), + ) + .into()); } if let Some(io_ref) = &io { @@ -1591,12 +1574,8 @@ impl Factory { fn get_user_dir() -> anyhow::Result<String> { let home = Platform::get_env("HOME").unwrap_or_default(); if home.is_empty() { - return Err(anyhow::anyhow!(RuntimeException { - message: - "The HOME or COMPOSER_HOME environment variable must be set for composer to run correctly" - .to_string(), - code: 0, - })); + return Err(RuntimeException::new("The HOME or COMPOSER_HOME environment variable must be set for composer to run correctly" + .to_string()).into()); } Ok(trim(&strtr(&home, "\\", "/"), Some("/"))) @@ -1615,20 +1594,19 @@ impl Factory { let result = match file_or_data { ValidateJsonInput::File(file) => file.validate_schema(schema, None), ValidateJsonInput::Data(data) => { - let source = source.ok_or_else(|| { - anyhow::anyhow!(InvalidArgumentException { - message: - "$source is required to be provided if $fileOrData is arbitrary data" - .to_string(), - code: 0, - }) + let source = source.ok_or_else(|| -> anyhow::Error { + InvalidArgumentException::new( + "$source is required to be provided if $fileOrData is arbitrary data" + .to_string(), + ) + .into() })?; JsonFile::validate_json_schema(source, &data, schema, None) } }; if let Err(e) = result { - if let Some(jve) = e.downcast_ref::<JsonValidationException>() { + if let Some(jve) = e.catch::<JsonValidationException>() { let msg = format!( "{}, this may result in errors and should be resolved:{} - {}", jve.get_message(), @@ -1638,10 +1616,7 @@ impl Factory { if let Some(io_ref) = io { io_ref.write_error3(&format!("<warning>{}</>", msg), true, crate::io::NORMAL); } else { - return Err(anyhow::anyhow!(UnexpectedValueException { - message: msg, - code: 0 - })); + return Err(UnexpectedValueException::new(msg).into()); } } else { return Err(e); diff --git a/crates/shirabe/src/filter/platform_requirement_filter/platform_requirement_filter_factory.rs b/crates/shirabe/src/filter/platform_requirement_filter/platform_requirement_filter_factory.rs index 12c69091..7f08e8dd 100644 --- a/crates/shirabe/src/filter/platform_requirement_filter/platform_requirement_filter_factory.rs +++ b/crates/shirabe/src/filter/platform_requirement_filter/platform_requirement_filter_factory.rs @@ -38,13 +38,10 @@ impl PlatformRequirementFilterFactory { list, )?)) } - other => Err(anyhow::anyhow!(InvalidArgumentException { - message: format!( - "PlatformRequirementFilter: Unknown $boolOrList parameter {}. Please report at https://github.com/composer/composer/issues/new.", - shirabe_php_shim::get_debug_type(&other) - ), - code: 0, - })), + other => Err(InvalidArgumentException::new(format!( + "PlatformRequirementFilter: Unknown $boolOrList parameter {}. Please report at https://github.com/composer/composer/issues/new.", + shirabe_php_shim::get_debug_type(&other) + )).into()), } } diff --git a/crates/shirabe/src/installed_versions.rs b/crates/shirabe/src/installed_versions.rs index ce78f013..6d702413 100644 --- a/crates/shirabe/src/installed_versions.rs +++ b/crates/shirabe/src/installed_versions.rs @@ -202,11 +202,10 @@ impl InstalledVersions { return Ok(implode(" || ", &ranges)); } - Err(OutOfBoundsException { - message: format!("Package \"{}\" is not installed", package_name), - code: 0, - } - .into()) + Err( + OutOfBoundsException::new(format!("Package \"{}\" is not installed", package_name)) + .into(), + ) } /// @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present @@ -229,11 +228,10 @@ impl InstalledVersions { .map(|s| s.to_string())); } - Err(OutOfBoundsException { - message: format!("Package \"{}\" is not installed", package_name), - code: 0, - } - .into()) + Err( + OutOfBoundsException::new(format!("Package \"{}\" is not installed", package_name)) + .into(), + ) } /// @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present @@ -256,11 +254,10 @@ impl InstalledVersions { .map(|s| s.to_string())); } - Err(OutOfBoundsException { - message: format!("Package \"{}\" is not installed", package_name), - code: 0, - } - .into()) + Err( + OutOfBoundsException::new(format!("Package \"{}\" is not installed", package_name)) + .into(), + ) } /// @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference @@ -283,11 +280,10 @@ impl InstalledVersions { .map(|s| s.to_string())); } - Err(OutOfBoundsException { - message: format!("Package \"{}\" is not installed", package_name), - code: 0, - } - .into()) + Err( + OutOfBoundsException::new(format!("Package \"{}\" is not installed", package_name)) + .into(), + ) } /// @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. @@ -309,11 +305,10 @@ impl InstalledVersions { }); } - Err(OutOfBoundsException { - message: format!("Package \"{}\" is not installed", package_name), - code: 0, - } - .into()) + Err( + OutOfBoundsException::new(format!("Package \"{}\" is not installed", package_name)) + .into(), + ) } pub fn get_root_package() -> IndexMap<String, PhpMixed> { diff --git a/crates/shirabe/src/installer.rs b/crates/shirabe/src/installer.rs index 9028af88..702569aa 100644 --- a/crates/shirabe/src/installer.rs +++ b/crates/shirabe/src/installer.rs @@ -28,6 +28,7 @@ pub use package_event::*; pub use package_events::*; pub use plugin_installer::*; pub use project_installer::*; +use shirabe_php_shim::Catch as _; pub use suggested_packages_reporter::*; use crate::io::io_interface; @@ -226,10 +227,11 @@ impl Installer { gc_disable(); if self.update_allow_list.is_some() && self.update_mirrors { - return Err(RuntimeException { - message: "The installer options updateMirrors and updateAllowList are mutually exclusive.".to_string(), - code: 0, - }.into()); + return Err(RuntimeException::new( + "The installer options updateMirrors and updateAllowList are mutually exclusive." + .to_string(), + ) + .into()); } let is_fresh_install = self @@ -535,7 +537,7 @@ impl Installer { }); } Err(e) => { - if let Some(te) = e.downcast_ref::<TransportException>() { + if let Some(te) = e.catch::<TransportException>() { self.io .error(&format!("Failed to audit {} packages.", target), &[]); if self.io.is_verbose() { @@ -570,27 +572,25 @@ impl Installer { let mut locked_repository: Option<crate::repository::LockArrayRepositoryHandle> = None; let try_load_locked = || -> anyhow::Result< - Result<Option<crate::repository::LockArrayRepositoryHandle>, ParsingException>, + Result<Option<crate::repository::LockArrayRepositoryHandle>, anyhow::Error>, > { - if self.locker.borrow_mut().is_locked() { - match self.locker.borrow_mut().get_locked_repository(true) { - Ok(r) => Ok(Ok(Some(r))), - Err(e) => match e.downcast::<ParsingException>() { - Ok(p) => Ok(Err(p)), - Err(other) => Err(other), - }, - } - } else { - Ok(Ok(None)) + if self.locker.borrow_mut().is_locked() { + match self.locker.borrow_mut().get_locked_repository(true) { + Ok(r) => Ok(Ok(Some(r))), + Err(e) if e.is_instanceof::<ParsingException>() => Ok(Err(e)), + Err(e) => Err(e), } - }; + } else { + Ok(Ok(None)) + } + }; match try_load_locked()? { Ok(r) => locked_repository = r, Err(e) => { if self.update_allow_list.is_some() || self.update_mirrors { // in case we are doing a partial update or updating mirrors, the lock file is needed so we error - return Err(e.into()); + return Err(e); } // otherwise, ignoring parse errors as the lock file will be regenerated from scratch when // doing a full update diff --git a/crates/shirabe/src/installer/installation_manager.rs b/crates/shirabe/src/installer/installation_manager.rs index 1f1c60a5..454dfa86 100644 --- a/crates/shirabe/src/installer/installation_manager.rs +++ b/crates/shirabe/src/installer/installation_manager.rs @@ -191,11 +191,7 @@ impl InstallationManager { } } - Err(InvalidArgumentException { - message: format!("Unknown installer type: {}", r#type), - code: 0, - } - .into()) + Err(InvalidArgumentException::new(format!("Unknown installer type: {}", r#type)).into()) } /// Checks whether provided package is installed in one of the registered installers. diff --git a/crates/shirabe/src/installer/library_installer.rs b/crates/shirabe/src/installer/library_installer.rs index e53b9546..6c94e0c1 100644 --- a/crates/shirabe/src/installer/library_installer.rs +++ b/crates/shirabe/src/installer/library_installer.rs @@ -210,14 +210,11 @@ impl LibraryInstaller { assert!( self.download_manager.is_some(), "{}", - LogicException { - message: format!( + LogicException::new(format!( "{} should be initialized with a fully loaded Composer instance to be able to install/... packages", "LibraryInstaller", - ), - code: 0, - } - .message + )) + .get_message() ); self.download_manager.as_ref().unwrap() @@ -345,10 +342,10 @@ impl InstallerInterface for LibraryInstaller { target: PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>> { if !repo.borrow_mut().has_package(initial.clone())? { - return Err(InvalidArgumentException { - message: format!("Package is not installed: {}", initial), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package is not installed: {}", + initial + )) .into()); } @@ -378,10 +375,10 @@ impl InstallerInterface for LibraryInstaller { package: PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>> { if !repo.borrow_mut().has_package(package.clone())? { - return Err(InvalidArgumentException { - message: format!("Package is not installed: {}", package), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package is not installed: {}", + package + )) .into()); } diff --git a/crates/shirabe/src/installer/metapackage_installer.rs b/crates/shirabe/src/installer/metapackage_installer.rs index 52c152ea..99d3786c 100644 --- a/crates/shirabe/src/installer/metapackage_installer.rs +++ b/crates/shirabe/src/installer/metapackage_installer.rs @@ -86,10 +86,10 @@ impl InstallerInterface for MetapackageInstaller { target: PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>> { if !repo.borrow_mut().has_package(initial.clone())? { - return Err(InvalidArgumentException { - message: format!("Package is not installed: {}", initial), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package is not installed: {}", + initial + )) .into()); } @@ -115,10 +115,10 @@ impl InstallerInterface for MetapackageInstaller { package: PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>> { if !repo.borrow_mut().has_package(package.clone())? { - return Err(InvalidArgumentException { - message: format!("Package is not installed: {}", package), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package is not installed: {}", + package + )) .into()); } diff --git a/crates/shirabe/src/installer/noop_installer.rs b/crates/shirabe/src/installer/noop_installer.rs index 95f31f2e..d36f5c9e 100644 --- a/crates/shirabe/src/installer/noop_installer.rs +++ b/crates/shirabe/src/installer/noop_installer.rs @@ -69,10 +69,10 @@ impl InstallerInterface for NoopInstaller { ) -> anyhow::Result<Option<PhpMixed>> { let mut repo = repo.borrow_mut(); if !repo.has_package(initial.clone())? { - return Err(InvalidArgumentException { - message: format!("Package is not installed: {}", initial), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package is not installed: {}", + initial + )) .into()); } @@ -91,10 +91,10 @@ impl InstallerInterface for NoopInstaller { ) -> anyhow::Result<Option<PhpMixed>> { let mut repo = repo.borrow_mut(); if !repo.has_package(package.clone())? { - return Err(InvalidArgumentException { - message: format!("Package is not installed: {}", package), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package is not installed: {}", + package + )) .into()); } repo.remove_package(package); diff --git a/crates/shirabe/src/installer/plugin_installer.rs b/crates/shirabe/src/installer/plugin_installer.rs index 7683a7d5..abe313b7 100644 --- a/crates/shirabe/src/installer/plugin_installer.rs +++ b/crates/shirabe/src/installer/plugin_installer.rs @@ -121,13 +121,10 @@ impl InstallerInterface for PluginInstaller { let extra = package.get_extra(); let class = extra.get("class").cloned().unwrap_or(PhpMixed::Null); if empty(&class) { - return Err(UnexpectedValueException { - message: format!( - "Error while installing {}, composer-plugin packages should have a class defined in their extra key to be usable.", - package.get_pretty_name() - ), - code: 0, - }.into()); + return Err(UnexpectedValueException::new(format!( + "Error while installing {}, composer-plugin packages should have a class defined in their extra key to be usable.", + package.get_pretty_name() + )).into()); } self.inner.download(package, prev_package).await diff --git a/crates/shirabe/src/installer/project_installer.rs b/crates/shirabe/src/installer/project_installer.rs index 59d44410..8b878352 100644 --- a/crates/shirabe/src/installer/project_installer.rs +++ b/crates/shirabe/src/installer/project_installer.rs @@ -52,10 +52,10 @@ impl InstallerInterface for ProjectInstaller { if std::path::Path::new(install_path).exists() && !self.filesystem.borrow().is_dir_empty(install_path) { - return Err(InvalidArgumentException { - message: format!("Project directory {} is not empty.", install_path), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Project directory {} is not empty.", + install_path + )) .into()); } if !std::path::Path::new(install_path).is_dir() { @@ -109,11 +109,7 @@ impl InstallerInterface for ProjectInstaller { _initial: PackageInterfaceHandle, _target: PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>> { - Err(InvalidArgumentException { - message: "not supported".to_string(), - code: 0, - } - .into()) + Err(InvalidArgumentException::new("not supported".to_string()).into()) } async fn uninstall( @@ -121,11 +117,7 @@ impl InstallerInterface for ProjectInstaller { _repo: &InstalledRepositoryInterfaceHandle, _package: PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>> { - Err(InvalidArgumentException { - message: "not supported".to_string(), - code: 0, - } - .into()) + Err(InvalidArgumentException::new("not supported".to_string()).into()) } fn get_install_path(&self, _package: PackageInterfaceHandle) -> Option<String> { diff --git a/crates/shirabe/src/io/base_io.rs b/crates/shirabe/src/io/base_io.rs index 5dd7682f..52ef9a82 100644 --- a/crates/shirabe/src/io/base_io.rs +++ b/crates/shirabe/src/io/base_io.rs @@ -145,13 +145,11 @@ pub trait BaseIO: IOInterface { } if !Preg::is_match(php_regex!(r"{^[.A-Za-z0-9_]+$}"), &token_str) { - return Err(anyhow::anyhow!(UnexpectedValueException { - message: format!( - "Your github oauth token for {} contains invalid characters: \"{}\"", - domain, token_str - ), - code: 0, - })); + return Err(UnexpectedValueException::new(format!( + "Your github oauth token for {} contains invalid characters: \"{}\"", + domain, token_str + )) + .into()); } self.check_and_set_authentication( domain, diff --git a/crates/shirabe/src/io/buffer_io.rs b/crates/shirabe/src/io/buffer_io.rs index bbe1db9f..d1224fdc 100644 --- a/crates/shirabe/src/io/buffer_io.rs +++ b/crates/shirabe/src/io/buffer_io.rs @@ -30,10 +30,9 @@ impl BufferIO { let stream = match fopen("php://memory", "rw") { Ok(stream) => stream, Err(_) => { - return Err(RuntimeException { - message: "Unable to open memory output stream".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "Unable to open memory output stream".to_string(), + ) .into()); } }; @@ -116,10 +115,7 @@ impl BufferIO { let mut input = self.inner.input.borrow_mut(); let Some(streamable) = input.as_streamable_mut() else { - return Err(RuntimeException { - message: "Setting the user inputs requires at least the version 3.2 of the symfony/console component.".to_string(), - code: 0, - } + return Err(RuntimeException::new("Setting the user inputs requires at least the version 3.2 of the symfony/console component.".to_string()) .into()); }; @@ -133,10 +129,9 @@ impl BufferIO { let stream = match fopen("php://memory", "r+") { Ok(stream) => stream, Err(_) => { - return Err(RuntimeException { - message: "Unable to open memory output stream".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "Unable to open memory output stream".to_string(), + ) .into()); } }; diff --git a/crates/shirabe/src/io/console_io.rs b/crates/shirabe/src/io/console_io.rs index 12b70318..443a3e51 100644 --- a/crates/shirabe/src/io/console_io.rs +++ b/crates/shirabe/src/io/console_io.rs @@ -348,7 +348,7 @@ impl ConsoleIO { let mut input = self.input.borrow_mut(); question_helper .ask(&mut *input, error_output, question)? - .map_err(anyhow::Error::new) + .map_err(anyhow::Error::from) } } @@ -490,18 +490,15 @@ impl IOInterfaceImmutable for ConsoleIO { >, > = Box::new(move |answer: Option<PhpMixed>| { validator(answer.unwrap_or(PhpMixed::Null)).map_err(|e| { - shirabe_external_packages::symfony::console::exception::InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: e.to_string(), - code: 0, - }, + shirabe_external_packages::symfony::console::exception::InvalidArgumentException::new( + e.to_string(), ) }) }); question.set_validator(Some(adapted)); question .set_max_attempts(attempts) - .map_err(|e| anyhow::anyhow!(e.0.message))?; + .map_err(|e| anyhow::anyhow!(e.0.get_message().to_string()))?; self.ask_question(&question) } diff --git a/crates/shirabe/src/json/json_file.rs b/crates/shirabe/src/json/json_file.rs index 1d6a75ce..2d061ced 100644 --- a/crates/shirabe/src/json/json_file.rs +++ b/crates/shirabe/src/json/json_file.rs @@ -11,6 +11,7 @@ use crate::util::Silencer; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; use shirabe_external_packages::seld::json_lint::{ParsingException, ParsingExceptionDetails}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ InvalidArgumentException, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE, PhpMixed, RuntimeException, UnexpectedValueException, dirname, file_exists, file_get_contents, @@ -108,10 +109,9 @@ impl JsonFile { io: Option<std::rc::Rc<std::cell::RefCell<dyn IOInterface>>>, ) -> anyhow::Result<Self> { if http_downloader.is_none() && Preg::is_match(php_regex!(r"{^https?://}i"), &path) { - return Err(InvalidArgumentException { - message: "http urls require a HttpDownloader instance to be passed".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "http urls require a HttpDownloader instance to be passed".to_string(), + ) .into()); } Ok(Self { @@ -145,10 +145,10 @@ impl JsonFile { .map(|s| s.to_string())) } else { if !Filesystem::is_readable(&self.path) { - return Err(RuntimeException { - message: format!("The file \"{}\" is not readable.", self.path), - code: 0, - } + return Err(RuntimeException::new(format!( + "The file \"{}\" is not readable.", + self.path + )) .into()); } if let Some(io) = &self.io @@ -173,17 +173,13 @@ impl JsonFile { Err(e) => { // TransportException keeps its message verbatim; any other exception is wrapped // with the "Could not read" prefix. - if let Some(te) = e.downcast_ref::<TransportException>() { - return Err(RuntimeException { - message: te.message.clone(), - code: 0, - } - .into()); - } - return Err(RuntimeException { - message: format!("Could not read {}\n\n{}", self.path, e), - code: 0, + if let Some(te) = e.catch::<TransportException>() { + return Err(RuntimeException::new(te.get_message().to_string()).into()); } + return Err(RuntimeException::new(format!( + "Could not read {}\n\n{}", + self.path, e + )) .into()); } }; @@ -191,11 +187,7 @@ impl JsonFile { let json = match json { Some(j) => j, None => { - return Err(RuntimeException { - message: format!("Could not read {}", self.path), - code: 0, - } - .into()); + return Err(RuntimeException::new(format!("Could not read {}", self.path)).into()); } }; @@ -230,21 +222,18 @@ impl JsonFile { let dir = dirname(&self.path); if !is_dir(&dir) { if file_exists(&dir) { - return Err(UnexpectedValueException { - message: format!( - "{} exists and is not a directory.", - realpath(&dir).unwrap_or_default(), - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "{} exists and is not a directory.", + realpath(&dir).unwrap_or_default(), + )) .into()); } // PHP: @mkdir($dir, 0777, true) if !Silencer::call(|| Ok(mkdir(&dir, 0o777, true))).unwrap_or(false) { - return Err(UnexpectedValueException { - message: format!("{} does not exist and could not be created.", dir), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "{} does not exist and could not be created.", + dir + )) .into()); } } @@ -305,10 +294,10 @@ impl JsonFile { /// @return true true on success pub fn validate_schema(&self, schema: i64, schema_file: Option<&str>) -> anyhow::Result<bool> { if !Filesystem::is_readable(&self.path) { - return Err(RuntimeException { - message: format!("The file \"{}\" is not readable.", self.path), - code: 0, - } + return Err(RuntimeException::new(format!( + "The file \"{}\" is not readable.", + self.path + )) .into()); } let content = file_get_contents(&self.path).unwrap_or_default(); @@ -446,10 +435,8 @@ impl JsonFile { data: &T, options: JsonEncodeOptions, ) -> anyhow::Result<String> { - let json = json_encode_ex(data, options.to_flags()).map_err(|err| RuntimeException { - message: format!("JSON encoding failed: {}", err), - code: 0, - })?; + let json = json_encode_ex(data, options.to_flags()) + .map_err(|err| RuntimeException::new(format!("JSON encoding failed: {}", err)))?; if options.pretty_print && options.indent != Self::INDENT_DEFAULT { // Pretty printing and not using default indentation diff --git a/crates/shirabe/src/json/json_manipulator.rs b/crates/shirabe/src/json/json_manipulator.rs index eec823b6..d66fe645 100644 --- a/crates/shirabe/src/json/json_manipulator.rs +++ b/crates/shirabe/src/json/json_manipulator.rs @@ -36,10 +36,9 @@ impl JsonManipulator { contents = "{}".to_string(); } if !Preg::is_match3(php_regex!("#^\\{(.*)\\}$#s"), &contents, None) { - return Err(InvalidArgumentException { - message: "The json file must be an object ({})".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "The json file must be an object ({})".to_string(), + ) .into()); } let newline = if strpos(&contents, "\r\n").is_some() { @@ -823,10 +822,10 @@ impl JsonManipulator { ); } } else { - return Err(LogicException { - message: format!("Nothing matched above for: {}", children), - code: 0, - } + return Err(LogicException::new(format!( + "Nothing matched above for: {}", + children + )) .into()); } } @@ -940,10 +939,7 @@ impl JsonManipulator { children_clean = Some(children.clone()); } - let children_clean = children_clean.ok_or_else(|| InvalidArgumentException { - message: "JsonManipulator: $childrenClean is not defined. Please report at https://github.com/composer/composer/issues/new.".to_string(), - code: 0, - })?; + let children_clean = children_clean.ok_or_else(|| InvalidArgumentException::new("JsonManipulator: $childrenClean is not defined. Please report at https://github.com/composer/composer/issues/new.".to_string()))?; // no child data left, $name was the only key in let mut empty_match: IndexMap<String, String> = IndexMap::new(); @@ -1113,11 +1109,9 @@ impl JsonManipulator { ); } } else { - return Err(LogicException { - message: format!("Nothing matched above for: {}", children), - code: 0, - } - .into()); + return Err( + LogicException::new(format!("Nothing matched above for: {}", children)).into(), + ); } self.contents = format!("{}{}{}", node_start, children, node_end); @@ -1132,10 +1126,9 @@ impl JsonManipulator { index: i64, ) -> anyhow::Result<bool> { if index < 0 { - return Err(InvalidArgumentException { - message: "Index can only be positive integer".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "Index can only be positive integer".to_string(), + ) .into()); } diff --git a/crates/shirabe/src/json/json_validation_exception.rs b/crates/shirabe/src/json/json_validation_exception.rs index a63edda3..cb7370d2 100644 --- a/crates/shirabe/src/json/json_validation_exception.rs +++ b/crates/shirabe/src/json/json_validation_exception.rs @@ -11,7 +11,7 @@ pub struct JsonValidationException { impl JsonValidationException { pub fn new(message: String, errors: Vec<String>) -> Self { Self { - inner: Exception { message, code: 0 }, + inner: Exception::new(message), errors, } } @@ -19,16 +19,10 @@ impl JsonValidationException { pub fn get_errors(&self) -> &Vec<String> { &self.errors } - - pub fn get_message(&self) -> &str { - &self.inner.message - } -} - -impl std::fmt::Display for JsonValidationException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.inner.message) - } } -impl std::error::Error for JsonValidationException {} +shirabe_php_shim::impl_php_exception!( + JsonValidationException, + inner, + r"Composer\Json\JsonValidationException" +); diff --git a/crates/shirabe/src/package/alias_package.rs b/crates/shirabe/src/package/alias_package.rs index 088cd6ab..52b6efa4 100644 --- a/crates/shirabe/src/package/alias_package.rs +++ b/crates/shirabe/src/package/alias_package.rs @@ -435,10 +435,9 @@ impl PackageInterface for AliasPackage { if let Some(existing) = self.repository.as_ref().and_then(|w| w.upgrade()) && !std::rc::Rc::ptr_eq(&existing, repository.as_rc()) { - return Err(LogicException { - message: "A package can only be added to one repository".to_string(), - code: 0, - } + return Err(LogicException::new( + "A package can only be added to one repository".to_string(), + ) .into()); } self.repository = Some(repository.downgrade()); diff --git a/crates/shirabe/src/package/archiver/archivable_files_finder.rs b/crates/shirabe/src/package/archiver/archivable_files_finder.rs index ad2b41ab..f60c57f1 100644 --- a/crates/shirabe/src/package/archiver/archivable_files_finder.rs +++ b/crates/shirabe/src/package/archiver/archivable_files_finder.rs @@ -28,10 +28,10 @@ impl ArchivableFilesFinder { let sources_real_path = realpath(sources); if sources_real_path.is_none() { - return Err(RuntimeException { - message: format!("Could not realpath() the source directory \"{}\"", sources), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not realpath() the source directory \"{}\"", + sources + )) .into()); } let sources = fs.normalize_path(&sources_real_path.unwrap()); diff --git a/crates/shirabe/src/package/archiver/archive_manager.rs b/crates/shirabe/src/package/archiver/archive_manager.rs index afc89f32..435843a6 100644 --- a/crates/shirabe/src/package/archiver/archive_manager.rs +++ b/crates/shirabe/src/package/archiver/archive_manager.rs @@ -114,10 +114,9 @@ impl ArchiveManager { ignore_filters: bool, ) -> anyhow::Result<String> { if format.is_empty() { - return Err(anyhow::anyhow!(InvalidArgumentException { - message: "Format must be specified".to_string(), - code: 0, - })); + return Err( + InvalidArgumentException::new("Format must be specified".to_string()).into(), + ); } let mut usable_archiver_idx: Option<usize> = None; @@ -131,10 +130,11 @@ impl ArchiveManager { let usable_archiver_idx = match usable_archiver_idx { Some(i) => i, None => { - return Err(anyhow::anyhow!(RuntimeException { - message: format!("No archiver found to support {} format", format), - code: 0, - })); + return Err(RuntimeException::new(format!( + "No archiver found to support {} format", + format + )) + .into()); } }; diff --git a/crates/shirabe/src/package/archiver/phar_archiver.rs b/crates/shirabe/src/package/archiver/phar_archiver.rs index fbab8c67..0968f2a4 100644 --- a/crates/shirabe/src/package/archiver/phar_archiver.rs +++ b/crates/shirabe/src/package/archiver/phar_archiver.rs @@ -101,10 +101,10 @@ impl ArchiverInterface for PharArchiver { } else if format == "tar.gz" || format == "tar.bz2" { let compress_algo = *compress_formats.get(format.as_str()).unwrap(); if !PharData::can_compress(compress_algo) { - return Err(RuntimeException { - message: format!("Can not compress to {} format", format), - code: 0, - } + return Err(RuntimeException::new(format!( + "Can not compress to {} format", + format + )) .into()); } if format == "tar.gz" && function_exists("gzcompress") { @@ -124,10 +124,10 @@ impl ArchiverInterface for PharArchiver { if compress_formats.contains_key(format.as_str()) { let compress_algo = *compress_formats.get(format.as_str()).unwrap(); if !PharData::can_compress(compress_algo) { - return Err(RuntimeException { - message: format!("Can not compress to {} format", format), - code: 0, - } + return Err(RuntimeException::new(format!( + "Can not compress to {} format", + format + )) .into()); } @@ -147,7 +147,7 @@ impl ArchiverInterface for PharArchiver { "Could not create archive '{}' from '{}': {}", target_outer, sources, e ); - anyhow::anyhow!(RuntimeException { message, code: 0 }) + RuntimeException::new(message).into() }) } diff --git a/crates/shirabe/src/package/archiver/zip_archiver.rs b/crates/shirabe/src/package/archiver/zip_archiver.rs index 186d76c7..e5bb613d 100644 --- a/crates/shirabe/src/package/archiver/zip_archiver.rs +++ b/crates/shirabe/src/package/archiver/zip_archiver.rs @@ -111,7 +111,7 @@ impl ArchiverInterface for ZipArchiver { sources, zip.get_status_string() ); - Err(RuntimeException { message, code: 0 }.into()) + Err(RuntimeException::new(message).into()) } fn supports(&self, format: String, _source_type: Option<String>) -> bool { diff --git a/crates/shirabe/src/package/loader/array_loader.rs b/crates/shirabe/src/package/loader/array_loader.rs index 0940183c..2c6d3971 100644 --- a/crates/shirabe/src/package/loader/array_loader.rs +++ b/crates/shirabe/src/package/loader/array_loader.rs @@ -69,23 +69,17 @@ impl ArrayLoader { class: &str, ) -> anyhow::Result<CompleteOrRootPackage> { if !config.contains_key("name") { - return Err(UnexpectedValueException { - message: format!( - "Unknown package has no name defined ({}).", - json_encode(&PhpMixed::Array(config.clone())).unwrap_or_default() - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Unknown package has no name defined ({}).", + json_encode(&PhpMixed::Array(config.clone())).unwrap_or_default() + )) .into()); } if !config.contains_key("version") || !is_scalar(config.get("version").unwrap()) { - return Err(UnexpectedValueException { - message: format!( - "Package {} has no version defined.", - config.get("name").and_then(|v| v.as_string()).unwrap_or("") - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Package {} has no version defined.", + config.get("name").and_then(|v| v.as_string()).unwrap_or("") + )) .into()); } let mut config_version = config.get("version").cloned().unwrap_or(PhpMixed::Null); @@ -118,14 +112,11 @@ impl ArrayLoader { { Ok(v) => version = v, Err(e) => { - return Err(UnexpectedValueException { - message: format!( - "Failed to normalize version for package \"{}\": {}", - config.get("name").and_then(|v| v.as_string()).unwrap_or(""), - e - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Failed to normalize version for package \"{}\": {}", + config.get("name").and_then(|v| v.as_string()).unwrap_or(""), + e + )) .into()); } } @@ -226,18 +217,14 @@ impl ArrayLoader { }) .unwrap_or(false); if !has_required { - return Err(UnexpectedValueException { - message: format!( - "Package {}'s source key should be specified as {{\"type\": ..., \"url\": ..., \"reference\": ...}},\n{} given.", - - config - .get("name") - .and_then(|v| v.as_string()) - .unwrap_or(""), - json_encode(&source).unwrap_or_default(), - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Package {}'s source key should be specified as {{\"type\": ..., \"url\": ..., \"reference\": ...}},\n{} given.", + config + .get("name") + .and_then(|v| v.as_string()) + .unwrap_or(""), + json_encode(&source).unwrap_or_default(), + )) .into()); } let source_map = source_map.unwrap(); @@ -270,18 +257,14 @@ impl ArrayLoader { .map(|m| m.contains_key("type") && m.contains_key("url")) .unwrap_or(false); if !has_required { - return Err(UnexpectedValueException { - message: format!( - "Package {}'s dist key should be specified as {{\"type\": ..., \"url\": ..., \"reference\": ..., \"shasum\": ...}},\n{} given.", - - config - .get("name") - .and_then(|v| v.as_string()) - .unwrap_or(""), - json_encode(&dist).unwrap_or_default(), - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Package {}'s dist key should be specified as {{\"type\": ..., \"url\": ..., \"reference\": ..., \"shasum\": ...}},\n{} given.", + config + .get("name") + .and_then(|v| v.as_string()) + .unwrap_or(""), + json_encode(&dist).unwrap_or_default(), + )) .into()); } let dist_map = dist_map.unwrap(); @@ -672,13 +655,10 @@ impl ArrayLoader { let parsed_constraint = match self.version_parser.parse_constraints(&constraint) { Ok(c) => c, Err(_e) => { - return Err(UnexpectedValueException { - message: format!( - "Link constraint in {} {} > {} should be a valid version constraint, got \"{}\"", - source, description, target, constraint - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Link constraint in {} {} > {} should be a valid version constraint, got \"{}\"", + source, description, target, constraint + )) .into()); } }; @@ -702,11 +682,9 @@ impl ArrayLoader { config: &IndexMap<String, PhpMixed>, ) -> anyhow::Result<Option<String>> { if !config.contains_key("version") || !is_scalar(config.get("version").unwrap()) { - return Err(UnexpectedValueException { - message: "no/invalid version defined".to_string(), - code: 0, - } - .into()); + return Err( + UnexpectedValueException::new("no/invalid version defined".to_string()).into(), + ); } let mut config_version = config.get("version").cloned().unwrap_or(PhpMixed::Null); if !is_string(&config_version) { diff --git a/crates/shirabe/src/package/loader/invalid_package_exception.rs b/crates/shirabe/src/package/loader/invalid_package_exception.rs index 2bd22c61..23251994 100644 --- a/crates/shirabe/src/package/loader/invalid_package_exception.rs +++ b/crates/shirabe/src/package/loader/invalid_package_exception.rs @@ -27,7 +27,7 @@ impl InvalidPackageException { .join("\n") ); Self { - inner: Exception { message, code: 0 }, + inner: Exception::new(message), errors, warnings, data, @@ -47,10 +47,8 @@ impl InvalidPackageException { } } -impl std::fmt::Display for InvalidPackageException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.inner.message) - } -} - -impl std::error::Error for InvalidPackageException {} +shirabe_php_shim::impl_php_exception!( + InvalidPackageException, + inner, + r"Composer\Package\Loader\InvalidPackageException" +); diff --git a/crates/shirabe/src/package/loader/json_loader.rs b/crates/shirabe/src/package/loader/json_loader.rs index c30fbe07..d9e7100a 100644 --- a/crates/shirabe/src/package/loader/json_loader.rs +++ b/crates/shirabe/src/package/loader/json_loader.rs @@ -34,10 +34,7 @@ impl JsonLoader { let config: IndexMap<String, PhpMixed> = match config { PhpMixed::Array(m) => m, _ => { - return Err(TypeError { - message: "Composer\\Package\\Loader\\LoaderInterface::load(): Argument #1 ($config) must be of type array".to_string(), - code: 0, - } + return Err(TypeError::new("Composer\\Package\\Loader\\LoaderInterface::load(): Argument #1 ($config) must be of type array".to_string()) .into()); } }; diff --git a/crates/shirabe/src/package/loader/root_package_loader.rs b/crates/shirabe/src/package/loader/root_package_loader.rs index 284bb175..e26a7e95 100644 --- a/crates/shirabe/src/package/loader/root_package_loader.rs +++ b/crates/shirabe/src/package/loader/root_package_loader.rs @@ -78,10 +78,7 @@ impl RootPackageLoader { config["name"].as_string().unwrap_or(""), false, ) { - return Err(anyhow::anyhow!(RuntimeException { - message: format!("Your package name {}", err), - code: 0, - })); + return Err(RuntimeException::new(format!("Your package name {}", err)).into()); } let mut auto_versioned = false; @@ -197,13 +194,10 @@ impl RootPackageLoader { let package_name = config["name"].as_string().unwrap_or("").to_string(); if links.contains_key(&package_name) { - return Err(anyhow::anyhow!(RuntimeException { - message: format!( - "Root package '{}' cannot require itself in its composer.json\nDid you accidentally name your root package after an external package?", - package_name - ), - code: 0, - })); + return Err(RuntimeException::new(format!( + "Root package '{}' cannot require itself in its composer.json\nDid you accidentally name your root package after an external package?", + package_name + )).into()); } } } @@ -216,10 +210,7 @@ impl RootPackageLoader { if let Some(err) = ValidatingArrayLoader::has_package_naming_error(link_name, true) { - return Err(anyhow::anyhow!(RuntimeException { - message: format!("{}.{}", link_type, err), - code: 0, - })); + return Err(RuntimeException::new(format!("{}.{}", link_type, err)).into()); } } } @@ -291,14 +282,11 @@ impl RootPackageLoader { return { panic!( "{}", - UnexpectedValueException { - message: format!( + UnexpectedValueException::new(format!( "Invalid alias definition in \"{}\": \"{}\". Aliases should be in the form \"exact-version as other-exact-version\".", req_name, req_version - ), - code: 0, - } - .message + )) + .get_message() ) }; } diff --git a/crates/shirabe/src/package/loader/validating_array_loader.rs b/crates/shirabe/src/package/loader/validating_array_loader.rs index a050f2d7..fc8eed00 100644 --- a/crates/shirabe/src/package/loader/validating_array_loader.rs +++ b/crates/shirabe/src/package/loader/validating_array_loader.rs @@ -1583,11 +1583,12 @@ impl LoaderInterface for ValidatingArrayLoader { } if !self.errors.borrow().is_empty() { - return Err(anyhow::anyhow!(InvalidPackageException::new( + return Err(InvalidPackageException::new( self.errors.borrow().clone(), self.warnings.borrow().clone(), config.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), - ))); + ) + .into()); } let package = self.loader.load( diff --git a/crates/shirabe/src/package/locker.rs b/crates/shirabe/src/package/locker.rs index a4d28861..3b20704a 100644 --- a/crates/shirabe/src/package/locker.rs +++ b/crates/shirabe/src/package/locker.rs @@ -26,6 +26,7 @@ use crate::util::ProcessExecutor; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; use shirabe_external_packages::seld::json_lint::ParsingException; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ DATE_RFC3339, LogicException, PhpMixed, RuntimeException, array_intersect, array_keys, array_map, array_merge, file_get_contents, filemtime, function_exists, hash, in_array_loose, @@ -194,10 +195,7 @@ impl Locker { if let Some(packages_dev) = lock_data.get("packages-dev").cloned() { locked_packages = array_merge(locked_packages, packages_dev); } else { - return Err(RuntimeException { - message: "The lock file does not contain require-dev information, run install with the --no-dev option or delete it and run composer update to generate a new lock file.".to_string(), - code: 0, - } + return Err(RuntimeException::new("The lock file does not contain require-dev information, run install with the --no-dev option or delete it and run composer update to generate a new lock file.".to_string()) .into()); } } @@ -270,12 +268,10 @@ impl Locker { return Ok(packages); } - Err(RuntimeException { - message: - "Your composer.lock is invalid. Run \"composer update\" to generate a new one." - .to_string(), - code: 0, - } + Err(RuntimeException::new( + "Your composer.lock is invalid. Run \"composer update\" to generate a new one." + .to_string(), + ) .into()) } @@ -438,10 +434,9 @@ impl Locker { } if !self.lock_file.exists() { - return Err(LogicException { - message: "No lockfile found. Unable to read locked packages".to_string(), - code: 0, - } + return Err(LogicException::new( + "No lockfile found. Unable to read locked packages".to_string(), + ) .into()); } @@ -582,7 +577,7 @@ impl Locker { let is_locked = match self.is_locked_result() { Ok(b) => b, Err(e) => { - if e.downcast_ref::<ParsingException>().is_some() { + if e.is_instanceof::<ParsingException>() { false } else { return Err(e); @@ -641,13 +636,10 @@ impl Locker { let contents = match contents { Some(s) => s, None => { - return Err(RuntimeException { - message: format!( - "Unable to read {} contents to update the lock file hash.", - composer_json.get_path() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Unable to read {} contents to update the lock file hash.", + composer_json.get_path() + )) .into()); } }; @@ -720,13 +712,10 @@ impl Locker { let version = package.get_pretty_version(); if name.is_empty() || version.is_empty() { - return Err(LogicException { - message: format!( - "Package \"{}\" has no version or name and can not be locked", - package, - ), - code: 0, - } + return Err(LogicException::new(format!( + "Package \"{}\" has no version or name and can not be locked", + package, + )) .into()); } diff --git a/crates/shirabe/src/package/package.rs b/crates/shirabe/src/package/package.rs index bc99d08a..0f1e7d23 100644 --- a/crates/shirabe/src/package/package.rs +++ b/crates/shirabe/src/package/package.rs @@ -732,10 +732,9 @@ impl PackageInterface for Package { if let Some(existing) = self.repository.as_ref().and_then(|w| w.upgrade()) && !std::rc::Rc::ptr_eq(&existing, repository.as_rc()) { - return Err(LogicException { - message: "A package can only be added to one repository".to_string(), - code: 0, - } + return Err(LogicException::new( + "A package can only be added to one repository".to_string(), + ) .into()); } self.repository = Some(repository.downgrade()); diff --git a/crates/shirabe/src/package/version/version_guesser.rs b/crates/shirabe/src/package/version/version_guesser.rs index 98853952..1968ff11 100644 --- a/crates/shirabe/src/package/version/version_guesser.rs +++ b/crates/shirabe/src/package/version/version_guesser.rs @@ -750,10 +750,9 @@ impl VersionGuesser { let version = match version { Some(v) if !v.is_empty() => v, _ => { - return Err(RuntimeException { - message: "COMPOSER_ROOT_VERSION not set or empty".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "COMPOSER_ROOT_VERSION not set or empty".to_string(), + ) .into()); } }; diff --git a/crates/shirabe/src/package/version/version_selector.rs b/crates/shirabe/src/package/version/version_selector.rs index 84f2beef..733f342d 100644 --- a/crates/shirabe/src/package/version/version_selector.rs +++ b/crates/shirabe/src/package/version/version_selector.rs @@ -76,13 +76,10 @@ impl VersionSelector { show_warnings: ShowWarnings, ) -> anyhow::Result<Option<crate::package::PackageInterfaceHandle>> { if !base_package::STABILITIES.contains_key(preferred_stability) { - return Err(shirabe_php_shim::UnexpectedValueException { - message: format!( - "Expected a valid stability name as 3rd argument, got {}", - preferred_stability - ), - code: 0, - } + return Err(shirabe_php_shim::UnexpectedValueException::new(format!( + "Expected a valid stability name as 3rd argument, got {}", + preferred_stability + )) .into()); } diff --git a/crates/shirabe/src/plugin/php_plugin_proxy.rs b/crates/shirabe/src/plugin/php_plugin_proxy.rs index 686c05bd..af9d04db 100644 --- a/crates/shirabe/src/plugin/php_plugin_proxy.rs +++ b/crates/shirabe/src/plugin/php_plugin_proxy.rs @@ -2057,10 +2057,9 @@ impl PhpPluginProxy { Ok(_) => Ok(()), // TODO(plugin): the original exception class is collapsed to RuntimeException on // this side of the boundary. - Err(throw) => Err(anyhow::anyhow!(shirabe_php_shim::RuntimeException { - message: throw.message, - code: throw.code, - })), + Err(throw) => { + Err(shirabe_php_shim::RuntimeException::with_code(throw.message, throw.code).into()) + } } } @@ -2080,10 +2079,9 @@ impl PhpPluginProxy { )?; match outcome { Ok(value) => Ok(value.to_php_mixed()?), - Err(throw) => Err(anyhow::anyhow!(shirabe_php_shim::RuntimeException { - message: throw.message, - code: throw.code, - })), + Err(throw) => { + Err(shirabe_php_shim::RuntimeException::with_code(throw.message, throw.code).into()) + } } } } @@ -2160,10 +2158,11 @@ impl EventSubscriberInterface for PhpPluginProxy { // TODO(plugin): the original exception class is collapsed to RuntimeException on // this side of the boundary. Err(throw) => { - return Err(anyhow::anyhow!(shirabe_php_shim::RuntimeException { - message: throw.message, - code: throw.code, - })); + return Err(shirabe_php_shim::RuntimeException::with_code( + throw.message, + throw.code, + ) + .into()); } }; decode_subscribed_events(&self.class, value) @@ -2191,10 +2190,11 @@ impl Capable for PhpPluginProxy { // TODO(plugin): the original exception class is collapsed to RuntimeException on // this side of the boundary. Err(throw) => { - return Err(anyhow::anyhow!(shirabe_php_shim::RuntimeException { - message: throw.message, - code: throw.code, - })); + return Err(shirabe_php_shim::RuntimeException::with_code( + throw.message, + throw.code, + ) + .into()); } }; // PHP: `(array) $plugin->getCapabilities()` — the interface declares no return type, @@ -2289,12 +2289,10 @@ fn decode_listener_priority( } fn subscribed_events_shape_error(class: &str, value: &PluginValue) -> anyhow::Error { - anyhow::anyhow!(shirabe_php_shim::RuntimeException { - message: format!( - "{class}::getSubscribedEvents() returned an unsupported shape over RPC: {value:?}" - ), - code: 0, - }) + shirabe_php_shim::RuntimeException::new(format!( + "{class}::getSubscribedEvents() returned an unsupported shape over RPC: {value:?}" + )) + .into() } impl Drop for PhpPluginProxy { @@ -2407,13 +2405,11 @@ impl PhpInstallerProxy { } fn unsupported_shape(&self, method: &str, value: &PluginValue) -> anyhow::Error { - anyhow::anyhow!(shirabe_php_shim::RuntimeException { - message: format!( - "{}::{method}() returned an unsupported shape over RPC: {value:?}", - self.handle.class - ), - code: 0, - }) + shirabe_php_shim::RuntimeException::new(format!( + "{}::{method}() returned an unsupported shape over RPC: {value:?}", + self.handle.class + )) + .into() } } @@ -2632,15 +2628,11 @@ impl CommandProvider for PhpCommandProviderProxy { PluginValue::List(items) => items, PluginValue::Array(map) => map.into_values().collect(), _ => { - return Err(anyhow::anyhow!( - shirabe_php_shim::UnexpectedValueException { - message: format!( - "Plugin capability {} failed to return an array from getCommands", - self.handle.class - ), - code: 0, - } - )); + return Err(shirabe_php_shim::UnexpectedValueException::new(format!( + "Plugin capability {} failed to return an array from getCommands", + self.handle.class + )) + .into()); } }; let mut commands: Vec<std::rc::Rc<std::cell::RefCell<dyn BaseCommand>>> = Vec::new(); @@ -2661,13 +2653,10 @@ impl CommandProvider for PhpCommandProviderProxy { } fn invalid_command_error(capability: &PhpObjHandle) -> anyhow::Error { - anyhow::anyhow!(shirabe_php_shim::UnexpectedValueException { - message: format!( - "Plugin capability {} returned an invalid value, we expected an array of Composer\\Command\\BaseCommand objects", - capability.class - ), - code: 0, - }) + shirabe_php_shim::UnexpectedValueException::new(format!( + "Plugin capability {} returned an invalid value, we expected an array of Composer\\Command\\BaseCommand objects", + capability.class + )).into() } impl Drop for PhpCommandProviderProxy { @@ -2820,12 +2809,11 @@ impl PhpConsoleApplicationContext { let app = match value { PluginValue::PhpHandle(app) => app, other => { - return Err(anyhow::anyhow!(shirabe_php_shim::RuntimeException { - message: format!( + return Err(shirabe_php_shim::RuntimeException::new( + format!( "__shirabe_console_application_boot returned an unsupported shape over RPC: {other:?}" - ), - code: 0, - })); + ) + ).into()); } }; *self.app.borrow_mut() = Some(app.clone()); @@ -3058,13 +3046,11 @@ impl PhpCommandProxy { method: &str, value: &PluginValue, ) -> anyhow::Error { - anyhow::anyhow!(shirabe_php_shim::RuntimeException { - message: format!( - "{}::{method}() returned an unsupported shape over RPC: {value:?}", - handle.class - ), - code: 0, - }) + shirabe_php_shim::RuntimeException::new(format!( + "{}::{method}() returned an unsupported shape over RPC: {value:?}", + handle.class + )) + .into() } } @@ -3081,14 +3067,11 @@ impl Command for PhpCommandProxy { ) -> anyhow::Result<i64> { let context = CONSOLE_APP_CONTEXT .with(|slot| slot.borrow().clone()) - .ok_or_else(|| { - anyhow::anyhow!(shirabe_php_shim::RuntimeException { - message: format!( + .ok_or_else(|| -> anyhow::Error { + shirabe_php_shim::RuntimeException::new(format!( "cannot run plugin-provided command {}: no worker-side console application context was published", self.handle.class - ), - code: 0, - }) + )).into() })?; let app = context.booted_app()?; let input_line = input.borrow().__to_string(); @@ -3110,13 +3093,12 @@ impl Command for PhpCommandProxy { ) -> anyhow::Result<i64> { // `run` above never reaches this template hook; a direct call would bypass the // worker-side binding, so it stays an explicit error. - Err(anyhow::anyhow!(shirabe_php_shim::RuntimeException { - message: format!( + Err(shirabe_php_shim::RuntimeException::new( + format!( "plugin-provided command {} executes in the PHP worker through run(); execute() must not be called directly", self.handle.class - ), - code: 0, - })) + ) + ).into()) } fn is_proxy_command(&self) -> bool { diff --git a/crates/shirabe/src/plugin/plugin_blocked_exception.rs b/crates/shirabe/src/plugin/plugin_blocked_exception.rs index fcf52ace..b6a6070a 100644 --- a/crates/shirabe/src/plugin/plugin_blocked_exception.rs +++ b/crates/shirabe/src/plugin/plugin_blocked_exception.rs @@ -8,14 +8,12 @@ pub struct PluginBlockedException(pub UnexpectedValueException); impl PluginBlockedException { pub fn new(message: String) -> Self { - Self(UnexpectedValueException { message, code: 0 }) + Self(UnexpectedValueException::new(message)) } } -impl std::fmt::Display for PluginBlockedException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl std::error::Error for PluginBlockedException {} +shirabe_php_shim::impl_php_exception!( + PluginBlockedException, + 0, + r"Composer\Plugin\PluginBlockedException" +); diff --git a/crates/shirabe/src/plugin/plugin_manager.rs b/crates/shirabe/src/plugin/plugin_manager.rs index 18239ce9..c68708a5 100644 --- a/crates/shirabe/src/plugin/plugin_manager.rs +++ b/crates/shirabe/src/plugin/plugin_manager.rs @@ -234,10 +234,7 @@ impl PluginManager { let requires_composer = match requires_composer { Some(r) => r, None => { - return Err(RuntimeException { - message: format!("Plugin {} is missing a require statement for a version of the composer-plugin-api package.", package.get_name()), - code: 0, - }.into()); + return Err(RuntimeException::new(format!("Plugin {} is missing a require statement for a version of the composer-plugin-api package.", package.get_name())).into()); } }; @@ -316,10 +313,7 @@ impl PluginManager { _ => false, }; if class_is_empty { - return Err(UnexpectedValueException { - message: format!("Error while installing {}, composer-plugin packages should have a class defined in their extra key to be usable.", package.get_pretty_name()), - code: 0, - }.into()); + return Err(UnexpectedValueException::new(format!("Error while installing {}, composer-plugin packages should have a class defined in their extra key to be usable.", package.get_pretty_name())).into()); } // PHP: is_array($extra['class']) ? $extra['class'] : [$extra['class']] — an associative // array iterates its values too, and a non-string entry reaches class_exists() where it @@ -470,14 +464,11 @@ impl PluginManager { if old_installer_plugin { if !self.php_runtime_is_a(&class, "Composer\\Installer\\InstallerInterface")? { - return Err(RuntimeException { - message: format!( - "Could not activate plugin \"{}\" as \"{}\" does not implement Composer\\Installer\\InstallerInterface", - package.get_name(), - class - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not activate plugin \"{}\" as \"{}\" does not implement Composer\\Installer\\InstallerInterface", + package.get_name(), + class + )) .into()); } self.io.write_error(&format!( @@ -511,14 +502,11 @@ impl PluginManager { .push(PluginOrInstaller::Installer(installer)); } else if self.php_runtime_class_exists(&class, true)? { if !self.php_runtime_is_a(&class, "Composer\\Plugin\\PluginInterface")? { - return Err(RuntimeException { - message: format!( - "Could not activate plugin \"{}\" as \"{}\" does not implement Composer\\Plugin\\PluginInterface", - package.get_name(), - class - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not activate plugin \"{}\" as \"{}\" does not implement Composer\\Plugin\\PluginInterface", + package.get_name(), + class + )) .into()); } let handle = self.php_runtime_new_object(&class)?; @@ -534,14 +522,11 @@ impl PluginManager { .or_default() .push(PluginOrInstaller::Plugin(plugin)); } else if fail_on_missing_classes { - return Err(UnexpectedValueException { - message: format!( - "Plugin {} could not be initialized, class not found: {}", - package.get_name(), - class - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Plugin {} could not be initialized, class not found: {}", + package.get_name(), + class + )) .into()); } } @@ -1033,14 +1018,11 @@ impl PluginManager { // || !trim(...)). Once the first branch has declined, a present key always fails one // of the three disjuncts, so a present key unconditionally throws here. if let Some(value) = capabilities.get(capability) { - return Err(UnexpectedValueException { - message: format!( - "Plugin {} provided invalid capability class name(s), got {}", - plugin.get_class_name(), - var_export(value, true) - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Plugin {} provided invalid capability class name(s), got {}", + plugin.get_class_name(), + var_export(value, true) + )) .into()); } @@ -1066,14 +1048,11 @@ impl PluginManager { Some(&mut PluginRpcDispatcher::default()), ))?; if !matches!(exists, PluginValue::Bool(true)) { - return Err(RuntimeException { - message: format!( - "Cannot instantiate Capability, as class {} from plugin {} does not exist.", - capability_class, - plugin.get_class_name() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Cannot instantiate Capability, as class {} from plugin {} does not exist.", + capability_class, + plugin.get_class_name() + )) .into()); } @@ -1116,12 +1095,9 @@ impl PluginManager { if !php_is_a(&handle, "Composer\\Plugin\\Capability\\Capability")? || !php_is_a(&handle, capability_class_name)? { - return Err(RuntimeException { - message: format!( - "Class {capability_class} must implement both Composer\\Plugin\\Capability\\Capability and {capability_class_name}." - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Class {capability_class} must implement both Composer\\Plugin\\Capability\\Capability and {capability_class_name}." + )) .into()); } diff --git a/crates/shirabe/src/question/strict_confirmation_question.rs b/crates/shirabe/src/question/strict_confirmation_question.rs index 5141eb58..0fed4378 100644 --- a/crates/shirabe/src/question/strict_confirmation_question.rs +++ b/crates/shirabe/src/question/strict_confirmation_question.rs @@ -71,11 +71,8 @@ impl StrictConfirmationQuestion { Box::new(|answer: Option<PhpMixed>| { let answer = answer.unwrap_or(PhpMixed::Null); if !is_bool(&answer) { - return Err(InvalidArgumentException( - shirabe_php_shim::InvalidArgumentException { - message: "Please answer yes, y, no, or n.".to_string(), - code: 0, - }, + return Err(InvalidArgumentException::new( + "Please answer yes, y, no, or n.".to_string(), )); } Ok(answer) diff --git a/crates/shirabe/src/repository/artifact_repository.rs b/crates/shirabe/src/repository/artifact_repository.rs index 99f340d1..2d16f137 100644 --- a/crates/shirabe/src/repository/artifact_repository.rs +++ b/crates/shirabe/src/repository/artifact_repository.rs @@ -47,10 +47,9 @@ impl ArtifactRepository { io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, ) -> anyhow::Result<Self> { if !extension_loaded("zip") { - return Err(RuntimeException { - message: "The artifact repository requires PHP's zip extension".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "The artifact repository requires PHP's zip extension".to_string(), + ) .into()); } @@ -171,13 +170,10 @@ impl ArtifactRepository { } else if file_extension == "zip" { file_type = "zip"; } else { - return Err(RuntimeException { - message: format!( - "Files with \"{}\" extensions aren't supported. Only ZIP and TAR/TAR.GZ/TGZ archives are supported.", - file_extension - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Files with \"{}\" extensions aren't supported. Only ZIP and TAR/TAR.GZ/TGZ archives are supported.", + file_extension + )) .into()); } @@ -228,10 +224,10 @@ impl ArtifactRepository { .unwrap_or_default(); match self.loader.load(cfg, None) { Ok(package) => Ok(Some(package)), - Err(exception) => Err(UnexpectedValueException { - message: format!("Failed loading package in {}: {}", pathname, exception), - code: 0, - } + Err(exception) => Err(UnexpectedValueException::new(format!( + "Failed loading package in {}: {}", + pathname, exception + )) .into()), } } diff --git a/crates/shirabe/src/repository/composer_repository.rs b/crates/shirabe/src/repository/composer_repository.rs index b2476e1e..5371c2c3 100644 --- a/crates/shirabe/src/repository/composer_repository.rs +++ b/crates/shirabe/src/repository/composer_repository.rs @@ -38,6 +38,7 @@ use futures::stream::FuturesOrdered; use indexmap::IndexMap; 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, @@ -183,10 +184,9 @@ impl ComposerRepository { .to_string(); repo_config.insert("url".to_string(), PhpMixed::String(url_after.clone())); if url_after.is_empty() { - return Err(InvalidArgumentException { - message: "The repository url must not be an empty string".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "The repository url must not be an empty string".to_string(), + ) .into()); } @@ -214,10 +214,10 @@ impl ComposerRepository { .and_then(|v| v.as_string()) .is_some_and(|s| !s.is_empty()); if url_bits_arr.is_none() || !scheme_present { - return Err(UnexpectedValueException { - message: format!("Invalid url given for Composer repository: {}", current_url), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Invalid url given for Composer repository: {}", + current_url + )) .into()); } @@ -387,12 +387,10 @@ impl ComposerRepository { if self.has_partial_packages()? { if self.partial_packages_by_name.is_none() { - return Err(LogicException { - message: - "hasPartialPackages failed to initialize $this->partialPackagesByName" - .to_string(), - code: 0, - } + return Err(LogicException::new( + "hasPartialPackages failed to initialize $this->partialPackagesByName" + .to_string(), + ) .into()); } @@ -403,17 +401,11 @@ impl ComposerRepository { .create_packages(flat, Some("packages.json inline packages".to_string())); } - return Err(LogicException { - message: "Composer repositories that have lazy providers and no available-packages list can not load the complete list of packages, use getPackageNames instead.".to_string(), - code: 0, - }.into()); + return Err(LogicException::new("Composer repositories that have lazy providers and no available-packages list can not load the complete list of packages, use getPackageNames instead.".to_string()).into()); } if has_providers { - return Err(LogicException { - message: "Composer repositories that have providers can not load the complete list of packages, use getPackageNames instead.".to_string(), - code: 0, - }.into()); + return Err(LogicException::new("Composer repositories that have providers can not load the complete list of packages, use getPackageNames instead.".to_string()).into()); } // PHP relies on ArrayRepository::getPackages() invoking the virtual initialize(), @@ -516,10 +508,9 @@ impl ComposerRepository { fn load_package_list(&mut self, package_filter: Option<&str>) -> anyhow::Result<Vec<String>> { if self.list_url.is_none() { - return Err(LogicException { - message: "Make sure to call loadRootServerFile before loadPackageList".to_string(), - code: 0, - } + return Err(LogicException::new( + "Make sure to call loadRootServerFile before loadPackageList".to_string(), + ) .into()); } @@ -636,10 +627,7 @@ impl ComposerRepository { let constraint = package_name_map.get(&name).and_then(|c| c.clone()); for (_uid, candidate) in candidates.iter() { if candidate.get_name() != name { - return Err(LogicException { - message: "whatProvides should never return a package with a different name than the requested one".to_string(), - code: 0, - }.into()); + return Err(LogicException::new("whatProvides should never return a package with a different name than the requested one".to_string()).into()); } names_found.insert(name.clone(), true); @@ -946,16 +934,13 @@ impl ComposerRepository { if !allow_partial_advisories && !is_full { let data_mixed = PhpMixed::Array(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect()); - return Err(RuntimeException { - message: format!( - "Advisory for {} could not be loaded as a full advisory from {}{}{}", - name, - repo_name, - PHP_EOL, - var_export(&data_mixed, true), - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Advisory for {} could not be loaded as a full advisory from {}{}{}", + name, + repo_name, + PHP_EOL, + var_export(&data_mixed, true), + )) .into()); } let affected_versions: &AnyConstraint = advisory.affected_versions(); @@ -1151,7 +1136,7 @@ impl ComposerRepository { ) { Ok(resp) => resp.decode_json()?, Err(e) => { - if let Some(te) = e.downcast_ref::<TransportException>() + if let Some(te) = e.catch::<TransportException>() && te.get_status_code() == Some(404) { return Ok(result); @@ -1183,11 +1168,10 @@ impl ComposerRepository { if self.has_partial_packages()? { if self.partial_packages_by_name.is_none() { - return Err(LogicException { - message: "hasPartialPackages failed to initialize $this->partialPackagesByName" + return Err(LogicException::new( + "hasPartialPackages failed to initialize $this->partialPackagesByName" .to_string(), - code: 0, - } + ) .into()); } for (_name, versions) in self.partial_packages_by_name.as_ref().unwrap().iter() { @@ -1438,7 +1422,7 @@ impl ComposerRepository { } Err(e) => { // 404s are acceptable for lazy provider repos - if let Some(te) = e.downcast_ref::<TransportException>() { + if let Some(te) = e.catch::<TransportException>() { let status_code = te.get_status_code(); if self.lazy_providers_url.is_some() && matches!(status_code, Some(404 | 499)) @@ -1726,12 +1710,10 @@ impl ComposerRepository { let mut names_found: IndexMap<String, bool> = IndexMap::new(); if self.lazy_providers_url.is_none() { - return Err(LogicException { - message: - "loadAsyncPackages only supports v2 protocol composer repos with a metadata-url" - .to_string(), - code: 0, - } + return Err(LogicException::new( + "loadAsyncPackages only supports v2 protocol composer repos with a metadata-url" + .to_string(), + ) .into()); } @@ -1964,10 +1946,7 @@ impl ComposerRepository { package_name: Option<&str>, ) -> anyhow::Result<PhpMixed> { if self.lazy_providers_url.is_none() { - return Err(LogicException { - message: "startCachedAsyncDownload only supports v2 protocol composer repos with a metadata-url".to_string(), - code: 0, - }.into()); + return Err(LogicException::new("startCachedAsyncDownload only supports v2 protocol composer repos with a metadata-url".to_string()).into()); } let name = strtolower(file_name); @@ -2142,13 +2121,11 @@ impl ComposerRepository { } if !extension_loaded("openssl") && self.url.starts_with("https") { - return Err(RuntimeException { - message: format!( - "You must enable the openssl extension in your php.ini to load information from {}", - self.url - ), - code: 0, - }.into()); + return Err(RuntimeException::new(format!( + "You must enable the openssl extension in your php.ini to load information from {}", + self.url + )) + .into()); } let mut data: Option<IndexMap<String, PhpMixed>> = None; @@ -2387,13 +2364,10 @@ impl ComposerRepository { api_url: api_url.clone(), }); if api_url.is_none() && !self.has_available_package_list { - return Err(UnexpectedValueException { - message: format!( - "Invalid security advisory configuration on {}: If the repository does not provide a security-advisories.api-url then available-packages or available-package-patterns are required to be provided for performance reason.", - self.get_repo_name() - ), - code: 0, - }.into()); + return Err(UnexpectedValueException::new(format!( + "Invalid security advisory configuration on {}: If the repository does not provide a security-advisories.api-url then available-packages or available-package-patterns are required to be provided for performance reason.", + self.get_repo_name() + )).into()); } } } @@ -2457,10 +2431,9 @@ impl ComposerRepository { fn canonicalize_url(&self, url: &str) -> anyhow::Result<String> { if url.is_empty() { - return Err(InvalidArgumentException { - message: "Expected a string with a value and not an empty string".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "Expected a string with a value and not an empty string".to_string(), + ) .into()); } @@ -2491,11 +2464,9 @@ impl ComposerRepository { let data = self.load_root_server_file(None)?; let data = match data { RootData::True => { - return Err(LogicException { - message: "loadRootServerFile should not return true during initialization" - .to_string(), - code: 0, - } + return Err(LogicException::new( + "loadRootServerFile should not return true during initialization".to_string(), + ) .into()); } RootData::Data(d) => d, @@ -2724,19 +2695,16 @@ impl ComposerRepository { })(); result.map_err(|e| { - RuntimeException { - message: format!( - "Could not load packages in {}{}: [{}] {}", - self.get_repo_name(), - source - .as_ref() - .map(|s| format!(" from {}", s)) - .unwrap_or_default(), - "Exception", - e - ), - code: 0, - } + RuntimeException::new(format!( + "Could not load packages in {}{}: [{}] {}", + self.get_repo_name(), + source + .as_ref() + .map(|s| format!(" from {}", s)) + .unwrap_or_default(), + "Exception", + e + )) .into() }) } @@ -2749,10 +2717,9 @@ impl ComposerRepository { store_last_modified_time: bool, ) -> anyhow::Result<IndexMap<String, PhpMixed>> { if filename.is_empty() { - return Err(InvalidArgumentException { - message: "$filename should not be an empty string".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "$filename should not be an empty string".to_string(), + ) .into()); } @@ -2830,13 +2797,10 @@ impl ComposerRepository { } // TODO use scarier wording once we know for sure it doesn't do false positives anymore - return Err(RepositorySecurityException(shirabe_php_shim::Exception { - message: format!( - "The contents of {} do not match its signature. This could indicate a man-in-the-middle attack or e.g. antivirus software corrupting files. Try running composer again and report this if you think it is a mistake.", - filename - ), - code: 0, - }).into()); + return Err(RepositorySecurityException::new(format!( + "The contents of {} do not match its signature. This could indicate a man-in-the-middle attack or e.g. antivirus software corrupting files. Try running composer again and report this if you think it is a mistake.", + filename + )).into()); } if let Some(dispatcher) = self.event_dispatcher.as_ref() { @@ -2904,15 +2868,15 @@ impl ComposerRepository { if e.downcast_ref::<RetryMarker>().is_some() { continue; } - if e.downcast_ref::<LogicException>().is_some() { + if e.is_instanceof::<LogicException>() { return Err(e); } - if let Some(te) = e.downcast_ref::<TransportException>() + if let Some(te) = e.catch::<TransportException>() && te.get_status_code() == Some(404) { return Err(e); } - if e.downcast_ref::<RepositorySecurityException>().is_some() { + if e.is_instanceof::<RepositorySecurityException>() { return Err(e); } @@ -2948,10 +2912,7 @@ impl ComposerRepository { match data { Some(d) => Ok(d), - None => Err(LogicException { - message: "ComposerRepository: Undefined $data. Please report at https://github.com/composer/composer/issues/new.".to_string(), - code: 0, - }.into()), + None => Err(LogicException::new("ComposerRepository: Undefined $data. Please report at https://github.com/composer/composer/issues/new.".to_string()).into()), } } @@ -2962,10 +2923,9 @@ impl ComposerRepository { last_modified_time: &str, ) -> anyhow::Result<FetchFileIfLastModifiedResult> { if filename.is_empty() { - return Err(InvalidArgumentException { - message: "$filename should not be an empty string".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "$filename should not be an empty string".to_string(), + ) .into()); } @@ -3079,10 +3039,10 @@ impl ComposerRepository { match result { Ok(v) => Ok(v), Err(e) => { - if e.downcast_ref::<LogicException>().is_some() { + if e.is_instanceof::<LogicException>() { return Err(e); } - if let Some(te) = e.downcast_ref::<TransportException>() + if let Some(te) = e.catch::<TransportException>() && te.get_status_code() == Some(404) { return Err(e); @@ -3109,10 +3069,9 @@ impl ComposerRepository { last_modified_time: Option<&str>, ) -> anyhow::Result<PhpMixed> { if filename.is_empty() { - return Err(InvalidArgumentException { - message: "$filename should not be an empty string".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "$filename should not be an empty string".to_string(), + ) .into()); } @@ -3263,7 +3222,7 @@ impl ComposerRepository { cache_key: &str, last_modified_time: Option<&str>, ) -> anyhow::Result<PhpMixed> { - if let Some(te) = e.downcast_ref::<TransportException>() + if let Some(te) = e.catch::<TransportException>() && te.get_status_code() == Some(404) { self.packages_not_found_cache @@ -3289,7 +3248,7 @@ impl ComposerRepository { } // special error code returned when network is being artificially disabled - if let Some(te) = e.downcast_ref::<TransportException>() + if let Some(te) = e.catch::<TransportException>() && te.get_status_code() == Some(499) { let resp = Response::new(self.url.clone(), Some(404), Vec::new(), Some(String::new())); @@ -3364,10 +3323,7 @@ impl ComposerRepository { /// @return true if the package name is present in availablePackages or matched by availablePackagePatterns pub(crate) fn lazy_providers_repo_contains(&self, name: &str) -> anyhow::Result<bool> { if !self.has_available_package_list { - return Err(LogicException { - message: "lazyProvidersRepoContains should not be called unless hasAvailablePackageList is true".to_string(), - code: 0, - }.into()); + return Err(LogicException::new("lazyProvidersRepoContains should not be called unless hasAvailablePackageList is true".to_string()).into()); } if let Some(ref available) = self.available_packages diff --git a/crates/shirabe/src/repository/filesystem_repository.rs b/crates/shirabe/src/repository/filesystem_repository.rs index 47241bde..d9085bfd 100644 --- a/crates/shirabe/src/repository/filesystem_repository.rs +++ b/crates/shirabe/src/repository/filesystem_repository.rs @@ -19,9 +19,9 @@ use crate::util::Filesystem; use crate::util::Platform; use indexmap::IndexMap; use shirabe_php_shim::{ - Exception, InvalidArgumentException, LogicException, PhpMixed, UnexpectedValueException, - array_flip, dirname, get_class_err, get_debug_type, in_array_strict, is_array, is_null, - is_string, ksort, realpath, str_repeat, usort, var_export, + AnyThrowable, InvalidArgumentException, LogicException, PhpClass as _, PhpMixed, + UnexpectedValueException, array_flip, dirname, get_debug_type, in_array_strict, is_array, + is_null, is_string, ksort, realpath, str_repeat, usort, var_export, }; use shirabe_semver::constraint::AnyConstraint; @@ -57,10 +57,9 @@ impl FilesystemRepository { let filesystem = filesystem .unwrap_or_else(|| std::rc::Rc::new(std::cell::RefCell::new(Filesystem::new(None)))); if dump_versions && root_package.is_none() { - return Err(InvalidArgumentException { - message: "Expected a root package instance if $dumpVersions is true".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "Expected a root package instance if $dumpVersions is true".to_string(), + ) .into()); } Ok(Self { @@ -134,10 +133,9 @@ impl FilesystemRepository { } if !is_array(&packages_value) { - return Err(UnexpectedValueException { - message: "Could not parse package list from the repository".to_string(), - code: 0, - } + return Err(UnexpectedValueException::new( + "Could not parse package list from the repository".to_string(), + ) .into()); } @@ -145,15 +143,14 @@ impl FilesystemRepository { })() { Ok(p) => p, Err(e) => { - return Err(InvalidRepositoryException(Exception { - message: format!( - "Invalid repository data in {}, packages could not be loaded: [{}] {}", - self.file.get_path(), - get_class_err(&e), - e, - ), - code: 0, - }) + return Err(InvalidRepositoryException::new(format!( + "Invalid repository data in {}, packages could not be loaded: [{}] {}", + self.file.get_path(), + AnyThrowable::of(e.as_ref()) + .expect("PHP reaches this only with a caught \\Throwable") + .php_class_name(), + e, + )) .into()); } }; @@ -464,12 +461,10 @@ impl FilesystemRepository { self.inner.get_packages()?.into_iter().collect(); let mut current_root: RootPackageInterfaceHandle = match &self.root_package { None => { - return Err(LogicException { - message: - "It should not be possible to dump packages if no root package is given" - .to_string(), - code: 0, - } + return Err(LogicException::new( + "It should not be possible to dump packages if no root package is given" + .to_string(), + ) .into()); } Some(r) => r.clone(), diff --git a/crates/shirabe/src/repository/filter_repository.rs b/crates/shirabe/src/repository/filter_repository.rs index d7be0a9f..98cef1d0 100644 --- a/crates/shirabe/src/repository/filter_repository.rs +++ b/crates/shirabe/src/repository/filter_repository.rs @@ -49,13 +49,10 @@ impl FilterRepository { )); } _ => { - return Err(InvalidArgumentException { - message: format!( - r#""only" key for repository {} should be an array"#, - repo.get_repo_name()? - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + r#""only" key for repository {} should be an array"#, + repo.get_repo_name()? + )) .into()); } } @@ -79,25 +76,19 @@ impl FilterRepository { )); } _ => { - return Err(InvalidArgumentException { - message: format!( - r#""exclude" key for repository {} should be an array"#, - repo.get_repo_name()? - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + r#""exclude" key for repository {} should be an array"#, + repo.get_repo_name()? + )) .into()); } } } if exclude.is_some() && only.is_some() { - return Err(InvalidArgumentException { - message: format!( - r#"Only one of "only" and "exclude" can be specified for repository {}"#, - repo.get_repo_name()? - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + r#"Only one of "only" and "exclude" can be specified for repository {}"#, + repo.get_repo_name()? + )) .into()); } if let Some(canonical_val) = options.get("canonical") { @@ -106,13 +97,10 @@ impl FilterRepository { canonical = *b; } _ => { - return Err(InvalidArgumentException { - message: format!( - r#""canonical" key for repository {} should be a boolean"#, - repo.get_repo_name()? - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + r#""canonical" key for repository {} should be a boolean"#, + repo.get_repo_name()? + )) .into()); } } diff --git a/crates/shirabe/src/repository/invalid_repository_exception.rs b/crates/shirabe/src/repository/invalid_repository_exception.rs index e7aa8509..ed84759c 100644 --- a/crates/shirabe/src/repository/invalid_repository_exception.rs +++ b/crates/shirabe/src/repository/invalid_repository_exception.rs @@ -8,14 +8,12 @@ pub struct InvalidRepositoryException(pub Exception); impl InvalidRepositoryException { pub fn new(message: String) -> Self { - Self(Exception { message, code: 0 }) + Self(Exception::new(message)) } } -impl std::fmt::Display for InvalidRepositoryException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl std::error::Error for InvalidRepositoryException {} +shirabe_php_shim::impl_php_exception!( + InvalidRepositoryException, + 0, + r"Composer\Repository\InvalidRepositoryException" +); diff --git a/crates/shirabe/src/repository/package_repository.rs b/crates/shirabe/src/repository/package_repository.rs index 62c8c5b3..810b0e13 100644 --- a/crates/shirabe/src/repository/package_repository.rs +++ b/crates/shirabe/src/repository/package_repository.rs @@ -16,7 +16,7 @@ use crate::repository::{ }; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; -use shirabe_php_shim::{Exception, PhpMixed, RuntimeException, php_regex, var_export}; +use shirabe_php_shim::{PhpMixed, RuntimeException, php_regex, var_export}; use shirabe_semver::constraint::AnyConstraint; #[derive(Debug)] @@ -72,10 +72,7 @@ impl PackageRepository { e, shirabe_php_shim::json_encode(package).unwrap_or_default() ); - return Ok(Err(InvalidRepositoryException(Exception { - message: msg, - code: 0, - }))); + return Ok(Err(InvalidRepositoryException::new(msg))); } }; self.inner.add_package(package_loaded)?; @@ -101,7 +98,7 @@ impl PackageRepository { // skips re-initializing it. fn ensure_initialized(&self) -> anyhow::Result<()> { if !self.inner.is_initialized() { - self.initialize()?.map_err(anyhow::Error::new)?; + self.initialize()?.map_err(anyhow::Error::from)?; } Ok(()) } @@ -234,15 +231,13 @@ impl AdvisoryProviderInterface for PackageRepository { }; if !allow_partial_advisories && matches!(advisory, AnySecurityAdvisory::Partial(_)) { - return Err(anyhow::anyhow!(RuntimeException { - message: format!( - "Advisory for {} could not be loaded as a full advisory from {}\n{}", - package_name, - self.get_repo_name()?, - var_export(data, true) - ), - code: 0, - })); + return Err(RuntimeException::new(format!( + "Advisory for {} could not be loaded as a full advisory from {}\n{}", + package_name, + self.get_repo_name()?, + var_export(data, true) + )) + .into()); } if !advisory.affected_versions().matches(package_constraint) { diff --git a/crates/shirabe/src/repository/path_repository.rs b/crates/shirabe/src/repository/path_repository.rs index ec2da043..313fc75e 100644 --- a/crates/shirabe/src/repository/path_repository.rs +++ b/crates/shirabe/src/repository/path_repository.rs @@ -56,11 +56,9 @@ impl PathRepository { process: Option<std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>, ) -> anyhow::Result<Self> { if !repo_config.contains_key("url") { - return Err(RuntimeException { - message: "You must specify the `url` configuration for the path repository" - .to_string(), - code: 0, - } + return Err(RuntimeException::new( + "You must specify the `url` configuration for the path repository".to_string(), + ) .into()); } @@ -172,13 +170,10 @@ impl PathRepository { } } - return Err(RuntimeException { - message: format!( - "The `url` supplied for the path ({}) repository does not exist", - self.url - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "The `url` supplied for the path ({}) repository does not exist", + self.url + )) .into()); } @@ -355,10 +350,10 @@ impl PathRepository { self.inner .add_package(self.loader.load(package.clone(), None).map_err(|e| { - RuntimeException { - message: format!("Failed loading the package in {}", composer_file_path), - code: 0, - } + RuntimeException::new(format!( + "Failed loading the package in {}", + composer_file_path + )) })?); } @@ -371,13 +366,10 @@ impl PathRepository { if defined("GLOB_BRACE") { flags |= GLOB_BRACE; } else if self.url.contains('{') || self.url.contains('}') { - return Err(RuntimeException { - message: format!( - "The operating system does not support GLOB_BRACE which is required for the url {}", - self.url - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "The operating system does not support GLOB_BRACE which is required for the url {}", + self.url + )) .into()); } diff --git a/crates/shirabe/src/repository/platform_repository.rs b/crates/shirabe/src/repository/platform_repository.rs index 944bb4db..0b32e157 100644 --- a/crates/shirabe/src/repository/platform_repository.rs +++ b/crates/shirabe/src/repository/platform_repository.rs @@ -65,24 +65,20 @@ impl PlatformRepository { let mut overrides_map: IndexMap<String, PlatformOverride> = IndexMap::new(); for (name, version) in overrides { if !is_string(&version) && !matches!(version, PhpMixed::Bool(false)) { - return Err(anyhow::anyhow!(UnexpectedValueException { - message: format!( - "config.platform.{} should be a string or false, but got {} {}", - name, - shirabe_php_shim::get_debug_type(&version), - var_export(&version, true) - ), - code: 0, - })); + return Err(UnexpectedValueException::new(format!( + "config.platform.{} should be a string or false, but got {} {}", + name, + shirabe_php_shim::get_debug_type(&version), + var_export(&version, true) + )) + .into()); } if name == "php" && matches!(version, PhpMixed::Bool(false)) { - return Err(anyhow::anyhow!(UnexpectedValueException { - message: format!( - "config.platform.{} cannot be set to false as you cannot disable php entirely.", - name - ), - code: 0, - })); + return Err(UnexpectedValueException::new(format!( + "config.platform.{} cannot be set to false as you cannot disable php entirely.", + name + )) + .into()); } overrides_map.insert( strtolower(&name), @@ -153,13 +149,11 @@ impl PlatformRepository { for r#override in &overrides { // Check that it's a platform package. if !Self::is_platform_package(&r#override.name) { - return Err(anyhow::anyhow!(InvalidArgumentException { - message: format!( - "Invalid platform package name in config.platform: {}", - r#override.name - ), - code: 0, - })); + return Err(InvalidArgumentException::new(format!( + "Invalid platform package name in config.platform: {}", + r#override.name + )) + .into()); } if !matches!(r#override.version, PhpMixed::Bool(false)) { @@ -1488,13 +1482,11 @@ impl PlatformRepository { pub fn add_package(&mut self, package: PackageInterfaceHandle) -> anyhow::Result<()> { if package.as_complete().is_none() { - return Err(anyhow::anyhow!(UnexpectedValueException { - message: format!( - "Expected CompletePackage but got {}", - get_class(&PhpMixed::Null) - ), - code: 0, - })); + return Err(UnexpectedValueException::new(format!( + "Expected CompletePackage but got {}", + get_class(&PhpMixed::Null) + )) + .into()); } let name = package.get_name(); diff --git a/crates/shirabe/src/repository/repository_factory.rs b/crates/shirabe/src/repository/repository_factory.rs index 856f58ad..a15d3a97 100644 --- a/crates/shirabe/src/repository/repository_factory.rs +++ b/crates/shirabe/src/repository/repository_factory.rs @@ -73,10 +73,7 @@ impl RepositoryFactory { repo_config.insert("json".to_string(), PhpMixed::String(repository.to_string())); return Ok(repo_config); } else { - return Err(InvalidArgumentException { - message: format!("Invalid repository URL ({}) given. This file does not contain a valid composer repository.", repository), - code: 0, - }.into()); + return Err(InvalidArgumentException::new(format!("Invalid repository URL ({}) given. This file does not contain a valid composer repository.", repository)).into()); } } @@ -87,10 +84,7 @@ impl RepositoryFactory { return Ok(repo_config); } - Err(InvalidArgumentException { - message: format!("Invalid repository url ({}) given. Has to be a .json file, an http url or a JSON object.", repository), - code: 0, - }.into()) + Err(InvalidArgumentException::new(format!("Invalid repository url ({}) given. Has to be a .json file, an http url or a JSON object.", repository)).into()) } pub fn from_string( @@ -121,13 +115,9 @@ impl RepositoryFactory { let repos = Self::create_repos(rm, vec![PhpMixed::Array(repo_config.into_iter().collect())])?; // PHP: return current($repos); - let (_, first) = repos - .into_iter() - .next() - .ok_or_else(|| UnexpectedValueException { - message: "create_repos returned no repository".to_string(), - code: 0, - })?; + let (_, first) = repos.into_iter().next().ok_or_else(|| { + UnexpectedValueException::new("create_repos returned no repository".to_string()) + })?; Ok(first) } @@ -149,10 +139,11 @@ impl RepositoryFactory { let rm = if let Some(rm) = rm { rm } else { - let io = io.ok_or_else(|| InvalidArgumentException { - message: "This function requires either an IOInterface or a RepositoryManager" - .to_string(), - code: 0, + let io = io.ok_or_else(|| { + InvalidArgumentException::new( + "This function requires either an IOInterface or a RepositoryManager" + .to_string(), + ) })?; owned_rm = Self::manager( io.clone(), @@ -243,21 +234,15 @@ impl RepositoryFactory { for (index, repo) in repo_configs.into_iter().enumerate() { match &repo { PhpMixed::String(_) => { - return Err(UnexpectedValueException { - message: "\"repositories\" should be an array of repository definitions, only a single repository was given".to_string(), - code: 0, - }.into()); + return Err(UnexpectedValueException::new("\"repositories\" should be an array of repository definitions, only a single repository was given".to_string()).into()); } PhpMixed::Array(repo_arr) => { if !repo_arr.contains_key("type") { - return Err(UnexpectedValueException { - message: format!( - "Repository \"{}\" ({}) must have a type defined", - index, - json_encode(&repo).unwrap_or_default() - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Repository \"{}\" ({}) must have a type defined", + index, + json_encode(&repo).unwrap_or_default() + )) .into()); } let repo_type = repo_arr @@ -296,15 +281,12 @@ impl RepositoryFactory { } } _ => { - return Err(UnexpectedValueException { - message: format!( - "Repository \"{}\" ({}) should be an array, {} given", - index, - json_encode(&repo).unwrap_or_default(), - get_debug_type(&repo) - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Repository \"{}\" ({}) should be an array, {} given", + index, + json_encode(&repo).unwrap_or_default(), + get_debug_type(&repo) + )) .into()); } } diff --git a/crates/shirabe/src/repository/repository_manager.rs b/crates/shirabe/src/repository/repository_manager.rs index 7fd2f80a..10ac973d 100644 --- a/crates/shirabe/src/repository/repository_manager.rs +++ b/crates/shirabe/src/repository/repository_manager.rs @@ -100,10 +100,10 @@ impl RepositoryManager { name: Option<&str>, ) -> anyhow::Result<RepositoryInterfaceHandle> { if !self.repository_classes.contains_key(r#type) { - return Err(InvalidArgumentException { - message: format!("Repository type is not registered: {}", r#type), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Repository type is not registered: {}", + r#type + )) .into()); } @@ -193,10 +193,10 @@ impl RepositoryManager { )), // TODO(plugin): `setRepositoryClass` lets a plugin register a repository class of // its own, which needs a Rust-side counterpart before it can be built here. - other => Err(anyhow::anyhow!(RuntimeException { - message: format!("Repository class has no Rust implementation: {other}"), - code: 0, - })), + other => Err(RuntimeException::new(format!( + "Repository class has no Rust implementation: {other}" + )) + .into()), } } diff --git a/crates/shirabe/src/repository/repository_security_exception.rs b/crates/shirabe/src/repository/repository_security_exception.rs index b517cad9..13f2fe72 100644 --- a/crates/shirabe/src/repository/repository_security_exception.rs +++ b/crates/shirabe/src/repository/repository_security_exception.rs @@ -6,10 +6,14 @@ use shirabe_php_shim::Exception; #[derive(Debug)] pub struct RepositorySecurityException(pub Exception); -impl std::fmt::Display for RepositorySecurityException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) +impl RepositorySecurityException { + pub fn new(message: String) -> Self { + Self(Exception::new(message)) } } -impl std::error::Error for RepositorySecurityException {} +shirabe_php_shim::impl_php_exception!( + RepositorySecurityException, + 0, + r"Composer\Repository\RepositorySecurityException" +); diff --git a/crates/shirabe/src/repository/repository_set.rs b/crates/shirabe/src/repository/repository_set.rs index def73f14..bd57ec20 100644 --- a/crates/shirabe/src/repository/repository_set.rs +++ b/crates/shirabe/src/repository/repository_set.rs @@ -21,6 +21,7 @@ use crate::repository::LockArrayRepositoryHandle; use crate::repository::PlatformRepository; use crate::repository::{FindPackageConstraint, RepositoryInterfaceHandle}; use indexmap::IndexMap; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{LogicException, RuntimeException, ksort, strtolower}; use shirabe_semver::constraint::AnyConstraint; use shirabe_semver::constraint::MatchAllConstraint; @@ -155,10 +156,7 @@ impl RepositorySet { /// @param RepositoryInterface $repo A package repository pub fn add_repository(&mut self, repo: RepositoryInterfaceHandle) -> anyhow::Result<()> { if self.locked { - return Err(RuntimeException { - message: "Pool has already been created from this repository set, it cannot be modified anymore.".to_string(), - code: 0, - } + return Err(RuntimeException::new("Pool has already been created from this repository set, it cannot be modified anymore.".to_string()) .into()); } @@ -374,17 +372,17 @@ impl RepositorySet { Err(e) => { // PHP catches only \Composer\Downloader\TransportException; other // exceptions propagate uncaught. - if e.downcast_ref::<TransportException>().is_none() { + if !e.is_instanceof::<TransportException>() { return Err(e); } if !ignore_unreachable { return Err(e); } let message = e - .downcast_ref::<TransportException>() + .catch::<TransportException>() .unwrap() - .message - .clone(); + .get_message() + .to_string(); unreachable_repos.push(message); } } @@ -482,11 +480,9 @@ impl RepositorySet { || repo_ref.as_any().is::<InstalledRepository>() }; if is_installed && !self.allow_installed_repositories { - return Err(LogicException { - message: "The pool can not accept packages from an installed repository" - .to_string(), - code: 0, - } + return Err(LogicException::new( + "The pool can not accept packages from an installed repository".to_string(), + ) .into()); } } @@ -505,11 +501,9 @@ impl RepositorySet { || repo_ref.as_any().is::<InstalledRepository>() }; if is_installed && !self.allow_installed_repositories { - return Err(LogicException { - message: "The pool can not accept packages from an installed repository" - .to_string(), - code: 0, - } + return Err(LogicException::new( + "The pool can not accept packages from an installed repository".to_string(), + ) .into()); } } @@ -582,10 +576,7 @@ impl RepositorySet { let mut allowed_packages: Vec<String> = vec![]; for package_name in &package_names { if PlatformRepository::is_platform_package(package_name) { - return Err(LogicException { - message: "createPoolForPackage(s) can not be used for platform packages, as they are never loaded by the PoolBuilder which expects them to be fixed. Use createPoolWithAllPackages or pass in a proper request with the platform packages you need fixed in it.".to_string(), - code: 0, - } + return Err(LogicException::new("createPoolForPackage(s) can not be used for platform packages, as they are never loaded by the PoolBuilder which expects them to be fixed. Use createPoolWithAllPackages or pass in a proper request with the platform packages you need fixed in it.".to_string()) .into()); } diff --git a/crates/shirabe/src/repository/vcs/forgejo_driver.rs b/crates/shirabe/src/repository/vcs/forgejo_driver.rs index 0719701f..e4ebe379 100644 --- a/crates/shirabe/src/repository/vcs/forgejo_driver.rs +++ b/crates/shirabe/src/repository/vcs/forgejo_driver.rs @@ -16,6 +16,7 @@ use crate::util::ForgejoUrl; use crate::util::http::Response; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ PhpMixed, RuntimeException, base64_decode, explode, extension_loaded, php_regex, urlencode, }; @@ -107,7 +108,7 @@ impl ForgejoDriver { ); let response = self .get_contents(&resource_url, false) - .map_err(|e| anyhow::anyhow!("{}", e.message))?; + .map_err(|e| anyhow::anyhow!("{}", e.get_message()))?; let mut resource = response.decode_json()?; // The Forgejo contents API only returns files up to 1MB as base64 encoded files; @@ -134,7 +135,7 @@ impl ForgejoDriver { if let Some(git_url) = git_url { resource = self .get_contents(&git_url, false) - .map_err(|e| anyhow::anyhow!("{}", e.message))? + .map_err(|e| anyhow::anyhow!("{}", e.get_message()))? .decode_json()?; } } @@ -157,22 +158,22 @@ impl ForgejoDriver { Some(b64) => match base64_decode(&b64) { Some(bytes) => match String::from_utf8(bytes) { Ok(s) => Ok(Some(s)), - Err(_) => Err(RuntimeException { - message: format!("Could not retrieve {} for {}", file, identifier), - code: 0, - } + Err(_) => Err(RuntimeException::new(format!( + "Could not retrieve {} for {}", + file, identifier + )) .into()), }, - None => Err(RuntimeException { - message: format!("Could not retrieve {} for {}", file, identifier), - code: 0, - } + None => Err(RuntimeException::new(format!( + "Could not retrieve {} for {}", + file, identifier + )) .into()), }, - None => Err(RuntimeException { - message: format!("Could not retrieve {} for {}", file, identifier), - code: 0, - } + None => Err(RuntimeException::new(format!( + "Could not retrieve {} for {}", + file, identifier + )) .into()), } } @@ -193,7 +194,7 @@ impl ForgejoDriver { ); let commit = self .get_contents(&resource_url, false) - .map_err(|e| anyhow::anyhow!("{}", e.message))? + .map_err(|e| anyhow::anyhow!("{}", e.get_message()))? .decode_json()?; let date_str = if let PhpMixed::Array(ref arr) = commit { @@ -208,9 +209,8 @@ impl ForgejoDriver { None }; - let date_str = date_str.ok_or_else(|| RuntimeException { - message: format!("Could not parse commit date for {}", identifier), - code: 0, + let date_str = date_str.ok_or_else(|| { + RuntimeException::new(format!("Could not parse commit date for {}", identifier)) })?; let date: chrono::DateTime<chrono::FixedOffset> = shirabe_php_shim::date_create(&date_str)?; @@ -243,7 +243,7 @@ impl ForgejoDriver { while let Some(url) = resource { let response = self .get_contents(&url, false) - .map_err(|e| anyhow::anyhow!("{}", e.message))?; + .map_err(|e| anyhow::anyhow!("{}", e.get_message()))?; let branch_data = response.decode_json()?; if let PhpMixed::List(ref list) = branch_data { for branch in list { @@ -286,7 +286,7 @@ impl ForgejoDriver { while let Some(url) = resource { let response = self .get_contents(&url, false) - .map_err(|e| anyhow::anyhow!("{}", e.message))?; + .map_err(|e| anyhow::anyhow!("{}", e.get_message()))?; let tags_data = response.decode_json()?; if let PhpMixed::List(ref list) = tags_data { for tag in list { @@ -599,7 +599,7 @@ impl ForgejoDriver { &mut self, url: &str, fetching_repo_data: bool, - ) -> anyhow::Result<Response, TransportException> { + ) -> anyhow::Result<Response, Box<TransportException>> { match self.inner.get_contents(url) { Ok(response) => Ok(response), Err(e) => match e.get_code() { @@ -610,14 +610,7 @@ impl ForgejoDriver { if !self.inner.io.is_interactive() { self.attempt_clone_fallback() - .map_err(|inner_e| TransportException { - message: inner_e.to_string(), - code: 0, - headers: None, - response: None, - status_code: None, - response_info: vec![], - })?; + .map_err(|inner_e| TransportException::new(inner_e.to_string(), 0))?; return Ok(Response::new( "dummy".to_string(), @@ -645,14 +638,7 @@ impl ForgejoDriver { ); let auth_result = forgejo .authorize_o_auth_interactively(&origin_url, message.as_deref()) - .map_err(|inner_e| TransportException { - message: inner_e.to_string(), - code: 0, - headers: None, - response: None, - status_code: None, - response_info: vec![], - })?; + .map_err(|inner_e| TransportException::new(inner_e.to_string(), 0))?; if let Ok(true) = auth_result { return self.inner.get_contents(url); @@ -734,7 +720,7 @@ impl crate::repository::vcs::VcsDriverInterface for ForgejoDriver { match self.get_composer_information(identifier) { Ok(info) => Ok(info.is_some()), Err(e) => { - if e.downcast_ref::<TransportException>().is_some() { + if e.is_instanceof::<TransportException>() { Ok(false) } else { Err(e) diff --git a/crates/shirabe/src/repository/vcs/fossil_driver.rs b/crates/shirabe/src/repository/vcs/fossil_driver.rs index 1b61ed4d..3795d1ca 100644 --- a/crates/shirabe/src/repository/vcs/fossil_driver.rs +++ b/crates/shirabe/src/repository/vcs/fossil_driver.rs @@ -12,6 +12,7 @@ use crate::util::ProcessExecutor; use chrono::{DateTime, FixedOffset, Utc}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ PhpMixed, RuntimeException, dirname, is_dir, is_file, is_writable, php_regex, }; @@ -77,10 +78,7 @@ impl FossilDriver { .unwrap_or("") .to_string(); if !Cache::is_usable(&cache_repo_dir) || !Cache::is_usable(&cache_vcs_dir) { - return Err(RuntimeException { - message: "FossilDriver requires a usable cache directory, and it looks like you set it to be disabled".to_string(), - code: 0, - } + return Err(RuntimeException::new("FossilDriver requires a usable cache directory, and it looks like you set it to be disabled".to_string()) .into()); } @@ -105,13 +103,10 @@ impl FossilDriver { None, ) != 0 { - return Err(RuntimeException { - message: format!( - "fossil was not found, check that it is installed and in your PATH env.\n\n{}", - self.inner.process.borrow().get_error_output() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "fossil was not found, check that it is installed and in your PATH env.\n\n{}", + self.inner.process.borrow().get_error_output() + )) .into()); } Ok(()) @@ -124,13 +119,10 @@ impl FossilDriver { fs.ensure_directory_exists(&self.checkout_dir)?; if !is_writable(dirname(&self.checkout_dir)) { - return Err(RuntimeException { - message: format!( - "Can not clone {} to access package information. The \"{}\" directory is not writable by the current user.", - self.inner.url, self.checkout_dir - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Can not clone {} to access package information. The \"{}\" directory is not writable by the current user.", + self.inner.url, self.checkout_dir + )) .into()); } @@ -173,13 +165,10 @@ impl FossilDriver { ) != 0 { let output = self.inner.process.borrow().get_error_output().to_string(); - return Err(RuntimeException { - message: format!( - "Failed to clone {} to repository {}\n\n{}", - self.inner.url, repo_file, output - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to clone {} to repository {}\n\n{}", + self.inner.url, repo_file, output + )) .into()); } @@ -192,13 +181,10 @@ impl FossilDriver { ) != 0 { let output = self.inner.process.borrow().get_error_output().to_string(); - return Err(RuntimeException { - message: format!( - "Failed to open repository {} in {}\n\n{}", - repo_file, self.checkout_dir, output - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to open repository {} in {}\n\n{}", + repo_file, self.checkout_dir, output + )) .into()); } } @@ -231,13 +217,10 @@ impl FossilDriver { pub fn get_file_content(&self, file: &str, identifier: &str) -> anyhow::Result<Option<String>> { if identifier.starts_with('-') { - return Err(RuntimeException { - message: format!( - "Invalid fossil identifier detected. Identifier must not start with a -, given: {}", - identifier - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Invalid fossil identifier detected. Identifier must not start with a -, given: {}", + identifier + )) .into()); } @@ -420,7 +403,7 @@ impl crate::repository::vcs::VcsDriverInterface for FossilDriver { match self.get_composer_information(identifier) { Ok(info) => Ok(info.is_some()), Err(e) => { - if e.downcast_ref::<TransportException>().is_some() { + if e.is_instanceof::<TransportException>() { Ok(false) } else { Err(e) diff --git a/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs b/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs index 8fe93018..d833b576 100644 --- a/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs +++ b/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs @@ -16,6 +16,7 @@ use crate::util::http::Response; use chrono::{DateTime, FixedOffset}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ InvalidArgumentException, LogicException, PhpMixed, RuntimeException, array_key_exists, array_search_mixed, extension_loaded, http_build_query_mixed, implode, is_array, php_regex, @@ -90,13 +91,10 @@ impl GitBitbucketDriver { &self.inner.url, Some(&mut m), ) { - return Err(InvalidArgumentException { - message: format!( - "The Bitbucket repository URL {} is invalid. It must be the HTTPS URL of a Bitbucket repository.", - self.inner.url.clone(), - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "The Bitbucket repository URL {} is invalid. It must be the HTTPS URL of a Bitbucket repository.", + self.inner.url.clone(), + )) .into()); } @@ -706,7 +704,7 @@ impl GitBitbucketDriver { if !self.inner.io.has_authentication(&self.inner.origin_url) && bitbucket_util.authorize_oauth(&self.inner.origin_url) { - return self.inner.get_contents(url).map_err(anyhow::Error::from); + return self.inner.get_contents(url).map_err(|e| (*e).into()); } if !self.inner.io.is_interactive() && fetching_repo_data { @@ -722,7 +720,7 @@ impl GitBitbucketDriver { } } - Err(e.into()) + Err((*e).into()) } } } @@ -742,7 +740,7 @@ impl GitBitbucketDriver { match self.setup_fallback_driver(&self.generate_ssh_url()) { Ok(()) => Ok(true), Err(e) => { - if e.downcast_ref::<RuntimeException>().is_some() { + if e.is_instanceof::<RuntimeException>() { self.fallback_driver = None; self.inner.io.write_error(&format!( @@ -799,11 +797,10 @@ impl GitBitbucketDriver { if self.root_identifier.is_none() { if !self.get_repo_data()? { if self.fallback_driver.is_none() { - return Err(LogicException { - message: "A fallback driver should be setup if getRepoData returns false" + return Err(LogicException::new( + "A fallback driver should be setup if getRepoData returns false" .to_string(), - code: 0, - } + ) .into()); } @@ -811,13 +808,10 @@ impl GitBitbucketDriver { } if self.vcs_type.as_deref() != Some("git") { - return Err(RuntimeException { - message: format!( - "{} does not appear to be a git repository, use {} but remember that Bitbucket no longer supports the mercurial repositories. https://bitbucket.org/blog/sunsetting-mercurial-support-in-bitbucket", - self.inner.url, self.clone_https_url - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "{} does not appear to be a git repository, use {} but remember that Bitbucket no longer supports the mercurial repositories. https://bitbucket.org/blog/sunsetting-mercurial-support-in-bitbucket", + self.inner.url, self.clone_https_url + )) .into()); } @@ -918,7 +912,7 @@ impl crate::repository::vcs::VcsDriverInterface for GitBitbucketDriver { match self.get_composer_information(identifier) { Ok(info) => Ok(info.is_some()), Err(e) => { - if e.downcast_ref::<TransportException>().is_some() { + if e.is_instanceof::<TransportException>() { Ok(false) } else { Err(e) diff --git a/crates/shirabe/src/repository/vcs/git_driver.rs b/crates/shirabe/src/repository/vcs/git_driver.rs index 1376ebbc..9f8a6b32 100644 --- a/crates/shirabe/src/repository/vcs/git_driver.rs +++ b/crates/shirabe/src/repository/vcs/git_driver.rs @@ -15,6 +15,7 @@ use chrono::TimeZone; use chrono::{DateTime, FixedOffset, Utc}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ InvalidArgumentException, RuntimeException, dirname, is_dir, is_writable, realpath, sys_get_temp_dir, @@ -52,13 +53,10 @@ impl GitDriver { if Filesystem::is_local_path(&self.inner.url) { self.inner.url = Preg::replace(php_regex!(r"{[\\/]\.git/?$}"), "", &self.inner.url); if !is_dir(&self.inner.url) { - return Err(RuntimeException { - message: format!( - "Failed to read package information from {} as the path does not exist", - self.inner.url - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to read package information from {} as the path does not exist", + self.inner.url + )) .into()); } self.repo_dir = self.inner.url.clone(); @@ -73,10 +71,7 @@ impl GitDriver { .unwrap_or("") .to_string(); if !Cache::is_usable(&cache_vcs_dir) { - return Err(RuntimeException { - message: "GitDriver requires a usable cache directory, and it looks like you set it to be disabled".to_string(), - code: 0, - } + return Err(RuntimeException::new("GitDriver requires a usable cache directory, and it looks like you set it to be disabled".to_string()) .into()); } @@ -96,25 +91,19 @@ impl GitDriver { fs.ensure_directory_exists(&dirname(&self.repo_dir))?; if !is_writable(dirname(&self.repo_dir)) { - return Err(RuntimeException { - message: format!( - "Can not clone {} to access package information. The \"{}\" directory is not writable by the current user.", - self.inner.url, - dirname(&self.repo_dir) - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Can not clone {} to access package information. The \"{}\" directory is not writable by the current user.", + self.inner.url, + dirname(&self.repo_dir) + )) .into()); } if Preg::is_match(php_regex!(r"{^ssh://[^@]+@[^:]+:[^0-9]+}"), &self.inner.url) { - return Err(InvalidArgumentException { - message: format!( - "The source URL {} is invalid, ssh URLs should have a port number after \":\".\nUse ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.", - self.inner.url - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "The source URL {} is invalid, ssh URLs should have a port number after \":\".\nUse ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.", + self.inner.url + )) .into()); } @@ -126,13 +115,10 @@ impl GitDriver { ); if !git_util.sync_mirror(&self.inner.url, &self.repo_dir)? { if !is_dir(&self.repo_dir) { - return Err(RuntimeException { - message: format!( - "Failed to clone {} to read package information from it", - self.inner.url - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to clone {} to read package information from it", + self.inner.url + )) .into()); } self.inner.io.write_error3(&format!( @@ -250,13 +236,10 @@ impl GitDriver { identifier: &str, ) -> anyhow::Result<Option<String>> { if identifier.starts_with('-') { - return Err(RuntimeException { - message: format!( - "Invalid git identifier detected. Identifier must not start with a -, given: {}", - identifier - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Invalid git identifier detected. Identifier must not start with a -, given: {}", + identifier + )) .into()); } @@ -283,13 +266,10 @@ impl GitDriver { identifier: &str, ) -> anyhow::Result<Option<DateTime<FixedOffset>>> { if identifier.starts_with('-') { - return Err(RuntimeException { - message: format!( - "Invalid git identifier detected. Identifier must not start with a -, given: {}", - identifier - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Invalid git identifier detected. Identifier must not start with a -, given: {}", + identifier + )) .into()); } @@ -459,7 +439,7 @@ impl GitDriver { ) { Ok(_) => Ok(true), Err(e) => { - if e.downcast_ref::<RuntimeException>().is_some() { + if e.is_instanceof::<RuntimeException>() { Ok(false) } else { Err(e) @@ -543,7 +523,7 @@ impl crate::repository::vcs::VcsDriverInterface for GitDriver { match self.get_composer_information(identifier) { Ok(info) => Ok(info.is_some()), Err(e) => { - if e.downcast_ref::<TransportException>().is_some() { + if e.is_instanceof::<TransportException>() { Ok(false) } else { Err(e) diff --git a/crates/shirabe/src/repository/vcs/github_driver.rs b/crates/shirabe/src/repository/vcs/github_driver.rs index d656ae61..72bcc8ae 100644 --- a/crates/shirabe/src/repository/vcs/github_driver.rs +++ b/crates/shirabe/src/repository/vcs/github_driver.rs @@ -15,6 +15,7 @@ use crate::util::http::Response; use chrono::{DateTime, FixedOffset}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ InvalidArgumentException, PhpMixed, RuntimeException, array_diff, array_key_exists, array_map, array_search_mixed, base64_decode, basename, empty, explode, extension_loaded, in_array_loose, @@ -77,13 +78,10 @@ impl GitHubDriver { &self.inner.url, Some(&mut match_), ) { - return Err(InvalidArgumentException { - message: format!( - "The GitHub repository URL {} is invalid.", - self.inner.url.clone(), - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "The GitHub repository URL {} is invalid.", + self.inner.url.clone(), + )) .into()); } @@ -737,7 +735,7 @@ impl GitHubDriver { ); let mut resource = self .get_contents(&resource_url, false) - .map_err(|e| anyhow::anyhow!("{}", e.message))? + .map_err(|e| anyhow::anyhow!("{}", e.get_message()))? .decode_json()?; // The GitHub contents API only returns files up to 1MB as base64 encoded files @@ -765,7 +763,7 @@ impl GitHubDriver { .to_string(); resource = self .get_contents(&git_url, false) - .map_err(|e| anyhow::anyhow!("{}", e.message))? + .map_err(|e| anyhow::anyhow!("{}", e.get_message()))? .decode_json()?; } @@ -789,10 +787,10 @@ impl GitHubDriver { let content = match content { Some(c) => String::from_utf8_lossy(&c).to_string(), None => { - return Err(RuntimeException { - message: format!("Could not retrieve {} for {}", file, identifier), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not retrieve {} for {}", + file, identifier + )) .into()); } }; @@ -817,7 +815,7 @@ impl GitHubDriver { ); let commit = self .get_contents(&resource, false) - .map_err(|e| anyhow::anyhow!("{}", e.message))? + .map_err(|e| anyhow::anyhow!("{}", e.get_message()))? .decode_json()?; let date_str = match commit { @@ -853,7 +851,7 @@ impl GitHubDriver { loop { let response = self .get_contents(resource.as_deref().unwrap_or(""), false) - .map_err(|e| anyhow::anyhow!("{}", e.message))?; + .map_err(|e| anyhow::anyhow!("{}", e.get_message()))?; let tags_data = response.decode_json()?; if let PhpMixed::List(ref list) = tags_data { for tag in list { @@ -903,7 +901,7 @@ impl GitHubDriver { loop { let response = self .get_contents(resource.as_deref().unwrap_or(""), false) - .map_err(|e| anyhow::anyhow!("{}", e.message))?; + .map_err(|e| anyhow::anyhow!("{}", e.get_message()))?; let branch_data = response.decode_json()?; if let PhpMixed::List(ref list) = branch_data { for branch in list { @@ -1015,7 +1013,7 @@ impl GitHubDriver { &mut self, url: &str, fetching_repo_data: bool, - ) -> anyhow::Result<Response, TransportException> { + ) -> anyhow::Result<Response, Box<TransportException>> { let response_result = self.inner.get_contents(url); match response_result { Ok(r) => Ok(r), @@ -1028,7 +1026,7 @@ impl GitHubDriver { ) .map_err(|err| TransportException::new(err.to_string(), 0))?; - match e.code { + match e.get_code() { 401 | 404 => { // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404 if !fetching_repo_data { @@ -1178,10 +1176,10 @@ impl GitHubDriver { }; } Err(e) => { - if e.code == 499 { + if e.get_code() == 499 { self.attempt_clone_fallback(Some(&e))?; } else { - return Err(e.into()); + return Err((*e).into()); } } } @@ -1232,13 +1230,11 @@ impl GitHubDriver { e: Option<&TransportException>, ) -> anyhow::Result<bool> { if !self.allow_git_fallback { - return Err(RuntimeException { - message: format!( - "Fallback to git driver disabled{}", - e.map(|e| format!(": {}", e.message)).unwrap_or_default() - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Fallback to git driver disabled{}", + e.map(|e| format!(": {}", e.get_message())) + .unwrap_or_default() + )) .into()); } @@ -1269,11 +1265,9 @@ impl GitHubDriver { pub(crate) fn setup_git_driver(&mut self, url: &str) -> anyhow::Result<()> { if !self.allow_git_fallback { - return Err(RuntimeException { - message: "Fallback to git driver disabled".to_string(), - code: 0, - } - .into()); + return Err( + RuntimeException::new("Fallback to git driver disabled".to_string()).into(), + ); } let mut repo_config: IndexMap<String, PhpMixed> = IndexMap::new(); repo_config.insert("url".to_string(), PhpMixed::String(url.to_string())); @@ -1377,7 +1371,7 @@ impl crate::repository::vcs::VcsDriverInterface for GitHubDriver { match self.get_composer_information(identifier) { Ok(info) => Ok(info.is_some()), Err(e) => { - if e.downcast_ref::<TransportException>().is_some() { + if e.is_instanceof::<TransportException>() { Ok(false) } else { Err(e) diff --git a/crates/shirabe/src/repository/vcs/gitlab_driver.rs b/crates/shirabe/src/repository/vcs/gitlab_driver.rs index 5fe513f6..8432ed65 100644 --- a/crates/shirabe/src/repository/vcs/gitlab_driver.rs +++ b/crates/shirabe/src/repository/vcs/gitlab_driver.rs @@ -16,6 +16,7 @@ use crate::util::http::Response; use chrono::{DateTime, FixedOffset}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ InvalidArgumentException, LogicException, PhpMixed, RuntimeException, array_search_mixed, array_shift, ctype_alnum, empty, explode, extension_loaded, implode, in_array_loose, is_array, @@ -82,13 +83,10 @@ impl GitLabDriver { pub fn initialize(&mut self) -> anyhow::Result<()> { let mut match_: IndexMap<CaptureKey, String> = IndexMap::new(); if !Preg::is_match3(Self::URL_REGEX, &self.inner.url, Some(&mut match_)) { - return Err(InvalidArgumentException { - message: format!( - "The GitLab repository URL {} is invalid. It must be the HTTP URL of a GitLab project.", - self.inner.url.clone(), - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "The GitLab repository URL {} is invalid. It must be the HTTP URL of a GitLab project.", + self.inner.url.clone(), + )) .into()); } @@ -134,13 +132,10 @@ impl GitLabDriver { let origin = match origin { Some(o) => o, None => { - return Err(LogicException { - message: format!( - "It should not be possible to create a gitlab driver with an unparsable origin URL ({})", - self.inner.url - ), - code: 0, - } + return Err(LogicException::new(format!( + "It should not be possible to create a gitlab driver with an unparsable origin URL ({})", + self.inner.url + )) .into()); } }; @@ -153,10 +148,9 @@ impl GitLabDriver { { // https treated as a synonym for http. if !matches!(protocol, "git" | "http" | "https") { - return Err(RuntimeException { - message: "gitlab-protocol must be one of git, http.".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "gitlab-protocol must be one of git, http.".to_string(), + ) .into()); } self.protocol = if protocol == "git" { @@ -424,8 +418,8 @@ impl GitLabDriver { let content = match self.get_contents(&resource, false) { Ok(response) => response.get_body().map(|s| s.to_string()), Err(e) => { - if e.code != 404 { - return Err(e.into()); + if e.get_code() != 404 { + return Err((*e).into()); } return Ok(None); @@ -617,7 +611,7 @@ impl GitLabDriver { loop { let response = self .get_contents(resource.as_deref().unwrap_or(""), false) - .map_err(|e| anyhow::anyhow!("{}", e.message))?; + .map_err(|e| anyhow::anyhow!("{}", e.get_message()))?; let data = response.decode_json()?; if let PhpMixed::List(ref list) = data { @@ -676,7 +670,7 @@ impl GitLabDriver { let resource = self.get_api_url(); let project = self .get_contents(&resource, true) - .map_err(|e| anyhow::anyhow!("{}", e.message))? + .map_err(|e| anyhow::anyhow!("{}", e.get_message()))? .decode_json()?; self.project = match project { PhpMixed::Array(m) => Some(m), @@ -769,7 +763,7 @@ impl GitLabDriver { &mut self, url: &str, fetching_repo_data: bool, - ) -> anyhow::Result<Response, TransportException> { + ) -> anyhow::Result<Response, Box<TransportException>> { let response_result = self.inner.get_contents(url); match response_result { Ok(response) => { @@ -839,21 +833,21 @@ impl GitLabDriver { .and_then(|v| v.as_string()) == Some("disabled") { - return Err(TransportException::new( + return Err(Box::new(TransportException::new( "The GitLab repository is disabled in the project".to_string(), 400, - )); + ))); } if !empty(&json_map.get("id").cloned().unwrap_or(PhpMixed::Null)) { self.is_private = false; } - return Err(TransportException::new( + return Err(Box::new(TransportException::new( "GitLab API seems to not be authenticated as it did not return a default_branch" .to_string(), 401, - )); + ))); } } @@ -868,7 +862,7 @@ impl GitLabDriver { ) .map_err(|err| TransportException::new(err.to_string(), 0))?; - match e.code { + match e.get_code() { 401 | 404 => { // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404 if !fetching_repo_data { @@ -901,7 +895,9 @@ impl GitLabDriver { self.inner.io.write_error3( &format!( "<warning>Failed to download {}/{}:{}</warning>", - self.namespace, self.repository, e.message + self.namespace, + self.repository, + e.get_message() ), true, io_interface::NORMAL, @@ -1142,7 +1138,7 @@ impl crate::repository::vcs::VcsDriverInterface for GitLabDriver { match self.get_composer_information(identifier) { Ok(info) => Ok(info.is_some()), Err(e) => { - if e.downcast_ref::<TransportException>().is_some() { + if e.is_instanceof::<TransportException>() { Ok(false) } else { Err(e) diff --git a/crates/shirabe/src/repository/vcs/hg_driver.rs b/crates/shirabe/src/repository/vcs/hg_driver.rs index 0283ed38..41aeebd9 100644 --- a/crates/shirabe/src/repository/vcs/hg_driver.rs +++ b/crates/shirabe/src/repository/vcs/hg_driver.rs @@ -12,6 +12,7 @@ use crate::util::Url; use chrono::{DateTime, FixedOffset, Utc}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{PhpMixed, RuntimeException, dirname, is_dir, is_writable, php_regex}; #[derive(Debug)] @@ -53,10 +54,7 @@ impl HgDriver { .unwrap_or("") .to_string(); if !Cache::is_usable(&cache_vcs_dir) { - return Err(RuntimeException { - message: "HgDriver requires a usable cache directory, and it looks like you set it to be disabled".to_string(), - code: 0, - }.into()); + return Err(RuntimeException::new("HgDriver requires a usable cache directory, and it looks like you set it to be disabled".to_string()).into()); } let sanitized = Preg::replace( @@ -70,13 +68,10 @@ impl HgDriver { fs.ensure_directory_exists(&cache_vcs_dir)?; if !is_writable(dirname(&self.repo_dir)) { - return Err(RuntimeException { - message: format!( - "Can not clone {} to access package information. The \"{}\" directory is not writable by the current user.", - self.inner.url, cache_vcs_dir - ), - code: 0, - }.into()); + return Err(RuntimeException::new(format!( + "Can not clone {} to access package information. The \"{}\" directory is not writable by the current user.", + self.inner.url, cache_vcs_dir + )).into()); } self.inner.config.borrow_mut().prohibit_url_by_config( @@ -167,13 +162,10 @@ impl HgDriver { pub fn get_file_content(&self, file: &str, identifier: &str) -> anyhow::Result<Option<String>> { if identifier.starts_with('-') { - return Err(RuntimeException { - message: format!( - "Invalid hg identifier detected. Identifier must not start with a -, given: {}", - identifier - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Invalid hg identifier detected. Identifier must not start with a -, given: {}", + identifier + )) .into()); } @@ -203,13 +195,10 @@ impl HgDriver { identifier: &str, ) -> anyhow::Result<Option<DateTime<FixedOffset>>> { if identifier.starts_with('-') { - return Err(RuntimeException { - message: format!( - "Invalid hg identifier detected. Identifier must not start with a -, given: {}", - identifier - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Invalid hg identifier detected. Identifier must not start with a -, given: {}", + identifier + )) .into()); } @@ -443,7 +432,7 @@ impl crate::repository::vcs::VcsDriverInterface for HgDriver { match self.get_composer_information(identifier) { Ok(info) => Ok(info.is_some()), Err(e) => { - if e.downcast_ref::<TransportException>().is_some() { + if e.is_instanceof::<TransportException>() { Ok(false) } else { Err(e) diff --git a/crates/shirabe/src/repository/vcs/perforce_driver.rs b/crates/shirabe/src/repository/vcs/perforce_driver.rs index 452ac5a2..af16cd8c 100644 --- a/crates/shirabe/src/repository/vcs/perforce_driver.rs +++ b/crates/shirabe/src/repository/vcs/perforce_driver.rs @@ -79,10 +79,7 @@ impl PerforceDriver { .unwrap_or("") .to_string(); if !Cache::is_usable(&cache_vcs_dir) { - return Err(RuntimeException { - message: "PerforceDriver requires a usable cache directory, and it looks like you set it to be disabled".to_string(), - code: 0, - }.into()); + return Err(RuntimeException::new("PerforceDriver requires a usable cache directory, and it looks like you set it to be disabled".to_string()).into()); } let repo_dir = format!("{}/{}", cache_vcs_dir, self.depot); @@ -180,11 +177,10 @@ impl PerforceDriver { } pub fn get_contents(&self, _url: &str) -> anyhow::Result<Response> { - Err(BadMethodCallException { - message: "Not implemented/used in PerforceDriver".to_string(), - code: 0, - } - .into()) + Err( + BadMethodCallException::new("Not implemented/used in PerforceDriver".to_string()) + .into(), + ) } pub fn supports( diff --git a/crates/shirabe/src/repository/vcs/svn_driver.rs b/crates/shirabe/src/repository/vcs/svn_driver.rs index 716b1943..84e3a20d 100644 --- a/crates/shirabe/src/repository/vcs/svn_driver.rs +++ b/crates/shirabe/src/repository/vcs/svn_driver.rs @@ -14,6 +14,7 @@ use crate::util::Url; use chrono::{DateTime, FixedOffset, Utc}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ PhpMixed, RuntimeException, php_regex, stripos, strrpos, strtr, substr, trim, }; @@ -199,14 +200,14 @@ impl SvnDriver { Ok(c) => c, Err(e) => { // PHP catches only TransportException; other exceptions propagate uncaught. - if e.downcast_ref::<TransportException>().is_none() { + if !e.is_instanceof::<TransportException>() { return Err(e); } let message = e - .downcast_ref::<TransportException>() + .catch::<TransportException>() .unwrap() - .message - .clone(); + .get_message() + .to_string(); if stripos(&message, "path not found").is_none() && stripos(&message, "svn: warning: W160013").is_none() { @@ -277,8 +278,8 @@ impl SvnDriver { ) { Ok(o) => o, Err(e) => { - if let Some(e) = e.downcast_ref::<RuntimeException>() { - return Err(TransportException::new(e.message.clone(), 0).into()); + if let Some(e) = e.catch::<RuntimeException>() { + return Err(TransportException::new(e.get_message().to_string(), 0).into()); } return Err(e); } @@ -567,24 +568,18 @@ impl SvnDriver { Ok(o) => Ok(o), Err(e) => { if self.util.as_mut().unwrap().binary_version().is_none() { - return Err(RuntimeException { - message: format!( - "Failed to load {}, svn was not found, check that it is installed and in your PATH env.\n\n{}", - self.inner.url, - self.inner.process.borrow().get_error_output(), - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Failed to load {}, svn was not found, check that it is installed and in your PATH env.\n\n{}", + self.inner.url, + self.inner.process.borrow().get_error_output(), + )) .into()); } - Err(RuntimeException { - message: format!( - "Repository {} could not be processed, {}", - self.inner.url, e, - ), - code: 0, - } + Err(RuntimeException::new(format!( + "Repository {} could not be processed, {}", + self.inner.url, e, + )) .into()) } } @@ -655,7 +650,7 @@ impl crate::repository::vcs::VcsDriverInterface for SvnDriver { match self.get_composer_information(identifier) { Ok(info) => Ok(info.is_some()), Err(e) => { - if e.downcast_ref::<TransportException>().is_some() { + if e.is_instanceof::<TransportException>() { Ok(false) } else { Err(e) diff --git a/crates/shirabe/src/repository/vcs/vcs_driver.rs b/crates/shirabe/src/repository/vcs/vcs_driver.rs index 3321201c..42f679df 100644 --- a/crates/shirabe/src/repository/vcs/vcs_driver.rs +++ b/crates/shirabe/src/repository/vcs/vcs_driver.rs @@ -13,6 +13,7 @@ use crate::util::http::Response; use chrono::{DateTime, FixedOffset}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{DATE_RFC3339, PhpMixed, extension_loaded, php_regex}; #[derive(Debug)] @@ -66,7 +67,7 @@ impl VcsDriverBase { "http" } - pub fn get_contents(&self, url: &str) -> anyhow::Result<Response, TransportException> { + pub fn get_contents(&self, url: &str) -> anyhow::Result<Response, Box<TransportException>> { let options_mixed = self .repo_config .get("options") @@ -79,9 +80,11 @@ impl VcsDriverBase { self.http_downloader .borrow_mut() .get(url, options) - .map_err(|e| match e.downcast::<TransportException>() { - Ok(te) => te, - Err(other) => TransportException::new(other.to_string(), 0), + .map_err(|e| { + Box::new(match e.catch::<TransportException>() { + Some(te) => te.clone(), + None => TransportException::new(e.to_string(), 0), + }) }) } @@ -299,7 +302,7 @@ pub trait VcsDriver: VcsDriverInterface { "http" } - fn get_contents(&self, url: &str) -> anyhow::Result<Response, TransportException> { + fn get_contents(&self, url: &str) -> anyhow::Result<Response, Box<TransportException>> { let options_mixed = self .repo_config() .get("options") @@ -312,9 +315,11 @@ pub trait VcsDriver: VcsDriverInterface { self.http_downloader() .borrow_mut() .get(url, options) - .map_err(|e| match e.downcast::<TransportException>() { - Ok(te) => te, - Err(other) => TransportException::new(other.to_string(), 0), + .map_err(|e| { + Box::new(match e.catch::<TransportException>() { + Some(te) => te.clone(), + None => TransportException::new(e.to_string(), 0), + }) }) } diff --git a/crates/shirabe/src/repository/vcs_repository.rs b/crates/shirabe/src/repository/vcs_repository.rs index 521d7aea..2b811c78 100644 --- a/crates/shirabe/src/repository/vcs_repository.rs +++ b/crates/shirabe/src/repository/vcs_repository.rs @@ -28,6 +28,7 @@ use crate::util::ProcessExecutor; use crate::util::Url; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ InvalidArgumentException, PhpClass, PhpMixed, php_regex, str_replace, strpos, }; @@ -305,10 +306,10 @@ impl VcsRepository { let driver_url = self.url.clone(); self.ensure_driver(); if self.driver.borrow().is_none() { - return Err(InvalidArgumentException { - message: format!("No driver found to handle VCS repository {}", driver_url), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "No driver found to handle VCS repository {}", + driver_url + )) .into()); } *self.version_parser.borrow_mut() = Some(VersionParser::new()); @@ -351,7 +352,7 @@ impl VcsRepository { } Ok(None) => {} Err(e) => { - if let Some(te) = e.downcast_ref::<TransportException>() + if let Some(te) = e.catch::<TransportException>() && self.should_rethrow_transport_exception(te) { return Err(e); @@ -367,7 +368,7 @@ impl VcsRepository { } } Err(e) => { - if let Some(te) = e.downcast_ref::<TransportException>() + if let Some(te) = e.catch::<TransportException>() && self.should_rethrow_transport_exception(te) { return Err(e); @@ -575,7 +576,7 @@ impl VcsRepository { Ok(()) })(); if let Err(e) = result { - if let Some(te) = e.downcast_ref::<TransportException>() { + if let Some(te) = e.catch::<TransportException>() { self.version_transport_exceptions .borrow_mut() .entry("tags".to_string()) @@ -589,7 +590,7 @@ impl VcsRepository { } } if is_very_verbose { - let detail = if let Some(te) = e.downcast_ref::<TransportException>() { + let detail = if let Some(te) = e.catch::<TransportException>() { format!( "no composer file was found ({} HTTP status code)", te.get_code() @@ -786,7 +787,7 @@ impl VcsRepository { Ok(()) })(); if let Err(e) = result { - if let Some(te) = e.downcast_ref::<TransportException>() { + if let Some(te) = e.catch::<TransportException>() { self.version_transport_exceptions .borrow_mut() .entry("branches".to_string()) diff --git a/crates/shirabe/src/self_update/versions.rs b/crates/shirabe/src/self_update/versions.rs index 14ad10d8..d3611ede 100644 --- a/crates/shirabe/src/self_update/versions.rs +++ b/crates/shirabe/src/self_update/versions.rs @@ -73,14 +73,11 @@ impl Versions { io: Option<std::rc::Rc<std::cell::RefCell<dyn IOInterface>>>, ) -> anyhow::Result<Result<(), InvalidArgumentException>> { if !Self::CHANNELS.contains(&channel.as_str()) { - return Ok(Err(InvalidArgumentException { - message: format!( - "Invalid channel {}, must be one of: {}", - channel, - Self::CHANNELS.join(", ") - ), - code: 0, - })); + return Ok(Err(InvalidArgumentException::new(format!( + "Invalid channel {}, must be one of: {}", + channel, + Self::CHANNELS.join(", ") + )))); } let channel_file = format!( @@ -146,13 +143,10 @@ impl Versions { } } - Ok(Err(UnexpectedValueException { - message: format!( - "There is no version of Composer available for your PHP version ({})", - PHP_VERSION - ), - code: 0, - })) + Ok(Err(UnexpectedValueException::new(format!( + "There is no version of Composer available for your PHP version ({})", + PHP_VERSION + )))) } fn get_versions_data(&mut self) -> anyhow::Result<PhpMixed> { diff --git a/crates/shirabe/src/util/auth_helper.rs b/crates/shirabe/src/util/auth_helper.rs index a323c37e..69a84857 100644 --- a/crates/shirabe/src/util/auth_helper.rs +++ b/crates/shirabe/src/util/auth_helper.rs @@ -80,11 +80,7 @@ impl AuthHelper { ) { return Ok(PhpMixed::String(input)); } - Err(RuntimeException { - message: "Please answer (y)es or (n)o".to_string(), - code: 0, - } - .into()) + Err(RuntimeException::new("Please answer (y)es or (n)o".to_string()).into()) }), None, PhpMixed::String("y".to_string()), diff --git a/crates/shirabe/src/util/bitbucket.rs b/crates/shirabe/src/util/bitbucket.rs index 50d4b700..d8e245a9 100644 --- a/crates/shirabe/src/util/bitbucket.rs +++ b/crates/shirabe/src/util/bitbucket.rs @@ -9,10 +9,11 @@ use crate::io::io_interface; use crate::util::HttpDownloader; use crate::util::ProcessExecutor; use indexmap::IndexMap; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{LogicException, PhpMixed, time}; fn transport_error_code(err: &anyhow::Error) -> Option<i64> { - err.downcast_ref::<TransportException>().map(|te| te.code) + err.catch::<TransportException>().map(|te| te.get_code()) } #[derive(Debug)] @@ -167,24 +168,18 @@ impl Bitbucket { let token_map = match token { PhpMixed::Array(ref m) => m.clone(), _ => { - return Err(LogicException { - message: format!( - "Expected a token configured with expires_in and access_token present, got {}", - shirabe_php_shim::json_encode(&token).unwrap_or_default() - ), - code: 0, - } + return Err(LogicException::new(format!( + "Expected a token configured with expires_in and access_token present, got {}", + shirabe_php_shim::json_encode(&token).unwrap_or_default() + )) .into()); } }; if !token_map.contains_key("expires_in") || !token_map.contains_key("access_token") { - return Err(LogicException { - message: format!( - "Expected a token configured with expires_in and access_token present, got {}", - shirabe_php_shim::json_encode(&token).unwrap_or_default() - ), - code: 0, - } + return Err(LogicException::new(format!( + "Expected a token configured with expires_in and access_token present, got {}", + shirabe_php_shim::json_encode(&token).unwrap_or_default() + )) .into()); } self.token = Some(token_map.into_iter().collect()); @@ -350,11 +345,7 @@ impl Bitbucket { match access_token { Some(t) => Ok(t), - None => Err(LogicException { - message: "Failed to initialize token above".to_string(), - code: 0, - } - .into()), + None => Err(LogicException::new("Failed to initialize token above".to_string()).into()), } } @@ -370,9 +361,10 @@ impl Bitbucket { .get_config_source_mut() .remove_config_setting(&format!("bitbucket-oauth.{}", origin_url))?; - let token = self.token.as_ref().ok_or_else(|| LogicException { - message: "Expected a token configured with expires_in present, got null".to_string(), - code: 0, + let token = self.token.as_ref().ok_or_else(|| { + LogicException::new( + "Expected a token configured with expires_in present, got null".to_string(), + ) })?; let expires_in = token .get("expires_in") @@ -380,13 +372,10 @@ impl Bitbucket { .ok_or_else(|| { let token_mixed = PhpMixed::Array(token.iter().map(|(k, v)| (k.clone(), v.clone())).collect()); - LogicException { - message: format!( - "Expected a token configured with expires_in present, got {}", - shirabe_php_shim::json_encode(&token_mixed).unwrap_or_default() - ), - code: 0, - } + LogicException::new(format!( + "Expected a token configured with expires_in present, got {}", + shirabe_php_shim::json_encode(&token_mixed).unwrap_or_default() + )) })?; let t = self.time.unwrap_or_else(time); diff --git a/crates/shirabe/src/util/config_validator.rs b/crates/shirabe/src/util/config_validator.rs index 9b6b292d..978a5cd2 100644 --- a/crates/shirabe/src/util/config_validator.rs +++ b/crates/shirabe/src/util/config_validator.rs @@ -10,6 +10,7 @@ use crate::package::loader::ValidatingArrayLoader; use indexmap::IndexMap; use serde::de::Error as _; use shirabe_external_packages::composer::pcre::Preg; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{PhpMixed, php_regex}; use shirabe_spdx_licenses::SpdxLicenses; @@ -55,7 +56,7 @@ impl ConfigValidator { match schema_result { Ok(()) => {} Err(e) => { - if let Some(validation_e) = e.downcast_ref::<JsonValidationException>() { + if let Some(validation_e) = e.catch::<JsonValidationException>() { for message in validation_e.get_errors() { if lax_valid { publish_errors.push(message.clone()); @@ -298,7 +299,7 @@ impl ConfigValidator { ) { Ok(_) => {} Err(e) => { - if let Some(invalid_e) = e.downcast_ref::<InvalidPackageException>() { + if let Some(invalid_e) = e.catch::<InvalidPackageException>() { errors.extend_from_slice(invalid_e.get_errors()); } } diff --git a/crates/shirabe/src/util/error_handler.rs b/crates/shirabe/src/util/error_handler.rs index 2e574fe0..dcea940e 100644 --- a/crates/shirabe/src/util/error_handler.rs +++ b/crates/shirabe/src/util/error_handler.rs @@ -58,13 +58,7 @@ impl ErrorHandler { return Ok(true); } - return Err(ErrorException { - message, - code: 0, - severity: level, - filename: file, - lineno: line, - }); + return Err(ErrorException::new(message, 0, level, file, line, None)); } let io = IO.with(|cell| cell.borrow().clone()); 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) { diff --git a/crates/shirabe/src/util/forgejo.rs b/crates/shirabe/src/util/forgejo.rs index 34d44b7b..6c18e4f6 100644 --- a/crates/shirabe/src/util/forgejo.rs +++ b/crates/shirabe/src/util/forgejo.rs @@ -6,6 +6,7 @@ use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; use crate::io::io_interface; use crate::util::HttpDownloader; +use shirabe_php_shim::Catch as _; #[derive(Debug)] pub struct Forgejo { @@ -121,8 +122,8 @@ impl Forgejo { Ok(_) => {} Err(e) => { let code = e - .downcast_ref::<crate::downloader::TransportException>() - .map(|te| te.code) + .catch::<crate::downloader::TransportException>() + .map(|te| te.get_code()) .unwrap_or(0); if [403, 401, 404].contains(&code) { self.io.write_error3( diff --git a/crates/shirabe/src/util/forgejo_url.rs b/crates/shirabe/src/util/forgejo_url.rs index ce5a8948..639426e1 100644 --- a/crates/shirabe/src/util/forgejo_url.rs +++ b/crates/shirabe/src/util/forgejo_url.rs @@ -27,10 +27,10 @@ impl ForgejoUrl { pub fn create(repo_url: &str) -> anyhow::Result<Self> { match Self::try_from(Some(repo_url)) { Some(url) => Ok(url), - None => Err(InvalidArgumentException { - message: format!("This is not a valid Forgejo URL: {}", repo_url), - code: 0, - } + None => Err(InvalidArgumentException::new(format!( + "This is not a valid Forgejo URL: {}", + repo_url + )) .into()), } } diff --git a/crates/shirabe/src/util/git.rs b/crates/shirabe/src/util/git.rs index a6f8d79e..3eec0947 100644 --- a/crates/shirabe/src/util/git.rs +++ b/crates/shirabe/src/util/git.rs @@ -64,11 +64,7 @@ impl Git { ); match io { None => { - return Err(RuntimeException { - message: msg, - code: 0, - } - .into()); + return Err(RuntimeException::new(msg).into()); } Some(io) => { io.write_error3( @@ -215,13 +211,10 @@ impl Git { }; if Preg::is_match(php_regex!(r"{^ssh://[^@]+@[^:]+:[^0-9]+}"), url) { - return Err(InvalidArgumentException { - message: format!( - "The source URL {} is invalid, ssh URLs should have a port number after \":\".\nUse ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.", - url - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "The source URL {} is invalid, ssh URLs should have a port number after \":\".\nUse ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.", + url + )) .into()); } @@ -1277,22 +1270,15 @@ impl Git { Option::<&str>::None, ) != 0 { - return Err(RuntimeException { - message: Url::sanitize(format!( - "Failed to clone {}, git was not found, check that it is installed and in your PATH env.\n\n{}", - url, - self.process.borrow().get_error_output() - )), - code: 0, - } + return Err(RuntimeException::new(Url::sanitize(format!( + "Failed to clone {}, git was not found, check that it is installed and in your PATH env.\n\n{}", + url, + self.process.borrow().get_error_output() + ))) .into()); } - Err(RuntimeException { - message: Url::sanitize(message.to_string()), - code: 0, - } - .into()) + Err(RuntimeException::new(Url::sanitize(message.to_string())).into()) } /// Retrieves the current git version. diff --git a/crates/shirabe/src/util/github.rs b/crates/shirabe/src/util/github.rs index 01a5dd1f..066f0ff5 100644 --- a/crates/shirabe/src/util/github.rs +++ b/crates/shirabe/src/util/github.rs @@ -9,6 +9,7 @@ use crate::util::HttpDownloader; use crate::util::ProcessExecutor; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{PhpMixed, date, in_array_loose, php_regex, stripos, strtolower}; #[derive(Debug)] @@ -233,7 +234,7 @@ impl GitHub { Ok(_) => {} Err(te) => { let code = te - .downcast_ref::<crate::downloader::TransportException>() + .catch::<crate::downloader::TransportException>() .map(|t| t.get_code()) .unwrap_or(0); if code == 403 || code == 401 { diff --git a/crates/shirabe/src/util/gitlab.rs b/crates/shirabe/src/util/gitlab.rs index c14ad46a..cdeb0f21 100644 --- a/crates/shirabe/src/util/gitlab.rs +++ b/crates/shirabe/src/util/gitlab.rs @@ -10,6 +10,7 @@ use crate::util::HttpDownloader; use crate::util::ProcessExecutor; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ PhpMixed, RuntimeException, http_build_query, in_array_strict, json_decode, php_regex, time, }; @@ -248,9 +249,9 @@ impl GitLab { Err(e) => { // 401 is bad credentials, // 403 is max login attempts exceeded - match e.downcast::<TransportException>() { - Ok(te) if te.code == 403 || te.code == 401 => { - if te.code == 401 { + match e.catch::<TransportException>() { + Some(te) if te.get_code() == 403 || te.get_code() == 401 => { + if te.get_code() == 401 { let response = te.get_response().and_then(|r| json_decode(r, true).ok()); let is_invalid_grant = response @@ -301,8 +302,7 @@ impl GitLab { continue; } - Ok(te) => return Err(te.into()), - Err(e) => return Err(e), + _ => return Err(e), } } }; @@ -360,10 +360,9 @@ impl GitLab { return Ok(true); } - Err(RuntimeException { - message: "Invalid GitLab credentials 5 times in a row, aborting.".to_string(), - code: 0, - } + Err(RuntimeException::new( + "Invalid GitLab credentials 5 times in a row, aborting.".to_string(), + ) .into()) } @@ -374,16 +373,17 @@ impl GitLab { ) -> anyhow::Result<bool> { let response = match self.refresh_token(scheme, origin_url) { Ok(r) => r, - Err(e) => match e.downcast::<TransportException>() { - Ok(te) => { + Err(e) => match e.catch::<TransportException>() { + Some(te) => { + let message = te.get_message().to_string(); self.io.write_error3( - &format!("Couldn't refresh access token: {}", te.message), + &format!("Couldn't refresh access token: {}", message), true, io_interface::NORMAL, ); return Ok(false); } - Err(e) => return Err(e), + None => return Err(e), }, }; @@ -488,10 +488,10 @@ impl GitLab { let refresh_token = match refresh_token { Some(t) => t, None => { - return Err(RuntimeException { - message: format!("No GitLab refresh token present for {}.", origin_url), - code: 0, - } + return Err(RuntimeException::new(format!( + "No GitLab refresh token present for {}.", + origin_url + )) .into()); } }; diff --git a/crates/shirabe/src/util/http/curl_downloader.rs b/crates/shirabe/src/util/http/curl_downloader.rs index 604dae77..6409c9d5 100644 --- a/crates/shirabe/src/util/http/curl_downloader.rs +++ b/crates/shirabe/src/util/http/curl_downloader.rs @@ -60,7 +60,7 @@ static TIMEOUT_WARNING: AtomicBool = AtomicBool::new(false); enum Decision { Retry { url: String, delay_ms: Option<u64> }, Done(Response), - Failed(TransportException), + Failed(anyhow::Error), } impl CurlDownloader { @@ -108,7 +108,7 @@ impl CurlDownloader { url: &str, mut options: IndexMap<String, PhpMixed>, copy_to: Option<&str>, - ) -> anyhow::Result<Result<Response, TransportException>> { + ) -> anyhow::Result<Result<Response, anyhow::Error>> { let mut attributes: IndexMap<String, PhpMixed> = { let mut m = IndexMap::new(); m.insert("retryAuthFailure".to_string(), PhpMixed::Bool(true)); @@ -178,7 +178,7 @@ impl CurlDownloader { .as_ref() .map(|pm| pm.get_proxy_for_request(url)) .transpose() - .map_err(|e| anyhow::anyhow!(e.message))? + .map_err(|e| anyhow::anyhow!(e.get_message().to_string()))? .and_then(|p| p.get_status(Some(" using proxy (%s)")).ok()) .unwrap_or_default(); // `attributes.redirects == 0 && attributes.retries == 0` in PHP is always true here since @@ -206,7 +206,7 @@ impl CurlDownloader { )?; let send_options = crate::util::StreamContextFactory::init_options(¤t_url, send_options, true) - .map_err(|e| anyhow::anyhow!(e.message))?; + .map_err(|e| anyhow::anyhow!(e.get_message().to_string()))?; let send_result = self .send_once(¤t_url, &send_options, copy_to, &attributes) @@ -304,26 +304,24 @@ impl CurlDownloader { if let Some(filename) = filename { unlink_silent(format!("{}~", filename)); } - // PHP throws a MaxFileSizeExceededException (a TransportException subclass) with - // the raw "Maximum allowed download size reached..." message verbatim rather than - // wrapping it in the generic curl-error text. + // The message carries the raw "Maximum allowed download size reached..." text + // rather than the generic curl-error wrapper used below. if transport_err.is_max_file_size { return Ok(Decision::Failed( - MaxFileSizeExceededException(TransportException::new( - transport_err.message, - 0, - )) - .0, + MaxFileSizeExceededException::new(transport_err.message).into(), )); } - return Ok(Decision::Failed(TransportException::new( - format!( - "curl error while downloading {}: {}", - Url::sanitize(url.to_string()), - transport_err.message - ), - 0, - ))); + return Ok(Decision::Failed( + TransportException::new( + format!( + "curl error while downloading {}: {}", + Url::sanitize(url.to_string()), + transport_err.message + ), + 0, + ) + .into(), + )); } }; @@ -373,7 +371,7 @@ impl CurlDownloader { }); } Ok(_) => {} - Err(e) => return Ok(Decision::Failed(e)), + Err(e) => return Ok(Decision::Failed((*e).into())), } // Handle 3xx redirects, 304 Not Modified excluded. @@ -401,7 +399,7 @@ impl CurlDownloader { if let Some(filename) = filename { unlink_silent(format!("{}~", filename)); } - return Ok(Decision::Failed(e)); + return Ok(Decision::Failed((*e).into())); } } } @@ -443,7 +441,7 @@ impl CurlDownloader { e.set_headers(curl_response.inner.get_headers().clone()); e.set_status_code(Some(curl_response.inner.get_status_code())); e.set_response(curl_response.inner.get_body().map(|s| s.to_string())); - return Ok(Decision::Failed(e)); + return Ok(Decision::Failed((*e).into())); } // storeAuth on success. @@ -625,7 +623,7 @@ impl CurlDownloader { url: &str, attributes: &IndexMap<String, PhpMixed>, response: &CurlResponse, - ) -> anyhow::Result<Result<String, TransportException>> { + ) -> anyhow::Result<Result<String, Box<TransportException>>> { let mut target_url = String::new(); if let Some(location_header) = response.inner.get_header("location") && !location_header.is_empty() @@ -682,14 +680,14 @@ impl CurlDownloader { return Ok(Ok(target_url)); } - Ok(Err(TransportException::new( + Ok(Err(Box::new(TransportException::new( format!( "The \"{}\" file could not be downloaded, got redirect without Location ({})", url, response.inner.get_status_message().unwrap_or_default() ), 0, - ))) + )))) } fn is_authenticated_retry_needed( @@ -699,7 +697,7 @@ impl CurlDownloader { filename: Option<&str>, attributes: &IndexMap<String, PhpMixed>, response: &CurlResponse, - ) -> anyhow::Result<Result<PromptAuthResult, TransportException>> { + ) -> anyhow::Result<Result<PromptAuthResult, Box<TransportException>>> { let retry_auth_failure = attributes .get("retryAuthFailure") .and_then(|b| b.as_bool()) @@ -808,7 +806,7 @@ impl CurlDownloader { filename: Option<&str>, response: &CurlResponse, error_message: &str, - ) -> TransportException { + ) -> Box<TransportException> { if let Some(filename) = filename { unlink_silent(format!("{}~", filename)); } @@ -836,13 +834,13 @@ impl CurlDownloader { ); } - TransportException::new( + Box::new(TransportException::new( format!( "The \"{}\" file could not be downloaded ({}){}", url, error_message, details ), response.inner.get_status_code(), - ) + )) } fn method_is_get(options: &IndexMap<String, PhpMixed>) -> bool { diff --git a/crates/shirabe/src/util/http/proxy_item.rs b/crates/shirabe/src/util/http/proxy_item.rs index 1a0b3ee8..73948f88 100644 --- a/crates/shirabe/src/util/http/proxy_item.rs +++ b/crates/shirabe/src/util/http/proxy_item.rs @@ -20,28 +20,22 @@ impl ProxyItem { let syntax_error = format!("unsupported `{}` syntax", env_name); if strpbrk(&proxy_url, "\r\n\t").is_some() { - return Err(RuntimeException { - message: syntax_error, - code: 0, - }); + return Err(RuntimeException::new(syntax_error)); } let proxy_parsed = parse_url_all(&proxy_url); let proxy = match proxy_parsed.as_array() { None => { - return Err(RuntimeException { - message: syntax_error, - code: 0, - }); + return Err(RuntimeException::new(syntax_error)); } Some(a) => a.clone(), }; if !proxy.contains_key("host") { - return Err(RuntimeException { - message: format!("unable to find proxy host in {}", env_name), - code: 0, - }); + return Err(RuntimeException::new(format!( + "unable to find proxy host in {}", + env_name + ))); } let scheme = if proxy.contains_key("scheme") { @@ -100,16 +94,16 @@ impl ProxyItem { // but is considered valid depending on the PHP or Curl version. let port = match port { None => { - return Err(RuntimeException { - message: format!("unable to find proxy port in {}", env_name), - code: 0, - }); + return Err(RuntimeException::new(format!( + "unable to find proxy port in {}", + env_name + ))); } Some(0) => { - return Err(RuntimeException { - message: format!("port 0 is reserved in {}", env_name), - code: 0, - }); + return Err(RuntimeException::new(format!( + "port 0 is reserved in {}", + env_name + ))); } Some(p) => p, }; diff --git a/crates/shirabe/src/util/http/proxy_manager.rs b/crates/shirabe/src/util/http/proxy_manager.rs index 82e8ebc5..13f0b521 100644 --- a/crates/shirabe/src/util/http/proxy_manager.rs +++ b/crates/shirabe/src/util/http/proxy_manager.rs @@ -71,12 +71,12 @@ impl ProxyManager { pub fn get_proxy_for_request( &self, request_url: &str, - ) -> Result<RequestProxy, TransportException> { + ) -> Result<RequestProxy, Box<TransportException>> { if let Some(ref error) = self.error { - return Err(TransportException::new( + return Err(Box::new(TransportException::new( format!("Unable to use a proxy: {}", error), 0, - )); + ))); } let scheme = request_url.split("://").next().unwrap_or("").to_string(); diff --git a/crates/shirabe/src/util/http/request_proxy.rs b/crates/shirabe/src/util/http/request_proxy.rs index 1262622f..78a64078 100644 --- a/crates/shirabe/src/util/http/request_proxy.rs +++ b/crates/shirabe/src/util/http/request_proxy.rs @@ -48,7 +48,7 @@ impl RequestProxy { pub fn get_curl_options( &self, ssl_options: &IndexMap<String, PhpMixed>, - ) -> Result<IndexMap<i64, PhpMixed>, TransportException> { + ) -> Result<IndexMap<i64, PhpMixed>, Box<TransportException>> { // PHP guards an HTTPS proxy behind `is_secure() && !supports_secure_proxy()` because // libcurl < 7.52.0 cannot speak TLS to a proxy. Shirabe always can (see // supports_secure_proxy), so the guard is dropped. @@ -90,10 +90,9 @@ impl RequestProxy { return Ok(format.replace("%s", self.status.as_deref().unwrap())); } - Err(InvalidArgumentException { - message: "String format specifier is missing".to_string(), - code: 0, - }) + Err(InvalidArgumentException::new( + "String format specifier is missing".to_string(), + )) } pub fn is_excluded_by_no_proxy(&self) -> bool { diff --git a/crates/shirabe/src/util/http_downloader.rs b/crates/shirabe/src/util/http_downloader.rs index b60664da..e53ad82f 100644 --- a/crates/shirabe/src/util/http_downloader.rs +++ b/crates/shirabe/src/util/http_downloader.rs @@ -17,6 +17,7 @@ use crate::util::http::Response; use crate::util::sync_executor; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ InvalidArgumentException, LogicException, PhpMixed, array_replace_recursive, extension_loaded, file_get_contents, function_exists, implode, is_numeric, php_regex, rawurldecode, @@ -205,19 +206,14 @@ impl HttpDownloader { return self.mock_get(url, &options); } if url.is_empty() { - return Err(InvalidArgumentException { - message: "$url must not be an empty string".to_string(), - code: 0, - } + return Err(InvalidArgumentException::new( + "$url must not be an empty string".to_string(), + ) .into()); } if !sync && !self.allow_async { - return Err(LogicException { - message: - "You must use the HttpDownloader instance which is part of a Composer\\Loop instance to be able to run async http requests" - .to_string(), - code: 0, - } + return Err(LogicException::new("You must use the HttpDownloader instance which is part of a Composer\\Loop instance to be able to run async http requests" + .to_string()) .into()); } @@ -321,7 +317,7 @@ impl HttpDownloader { let curl = self.curl.as_ref().unwrap(); return match curl.download(&origin, url, options, copy_to).await { Ok(Ok(response)) => Ok(response), - Ok(Err(transport_exception)) => Err(transport_exception.into()), + Ok(Err(e)) => Err(e), Err(e) => Err(e), }; } @@ -463,7 +459,7 @@ impl HttpDownloader { /// @internal pub fn get_exception_hints(e: &anyhow::Error) -> Option<Vec<String>> { - let e_as_transport: Option<&TransportException> = e.downcast_ref::<TransportException>(); + let e_as_transport: Option<&TransportException> = e.catch::<TransportException>(); e_as_transport?; let e_as_transport = e_as_transport.unwrap(); @@ -600,11 +596,7 @@ impl HttpDownloader { options: &IndexMap<String, PhpMixed>, ) -> anyhow::Result<Response> { if file_url.is_empty() { - return Err(LogicException { - message: "url cannot be an empty string".to_string(), - code: 0, - } - .into()); + return Err(LogicException::new("url cannot be an empty string".to_string()).into()); } let mock = self diff --git a/crates/shirabe/src/util/no_proxy_pattern.rs b/crates/shirabe/src/util/no_proxy_pattern.rs index 582fdb68..83adb09f 100644 --- a/crates/shirabe/src/util/no_proxy_pattern.rs +++ b/crates/shirabe/src/util/no_proxy_pattern.rs @@ -153,27 +153,24 @@ impl NoProxyPattern { let mask = network.netmask.as_deref().unwrap_or_default(); let ip = target.ip.as_slice(); if net.is_empty() { - return Err(RuntimeException { - message: format!( - "Could not parse network IP {}", - String::from_utf8_lossy(net) - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not parse network IP {}", + String::from_utf8_lossy(net) + )) .into()); } if mask.is_empty() { - return Err(RuntimeException { - message: format!("Could not parse netmask {}", String::from_utf8_lossy(mask)), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not parse netmask {}", + String::from_utf8_lossy(mask) + )) .into()); } if ip.is_empty() { - return Err(RuntimeException { - message: format!("Could not parse target IP {}", String::from_utf8_lossy(ip)), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not parse target IP {}", + String::from_utf8_lossy(ip) + )) .into()); } @@ -327,23 +324,17 @@ impl NoProxyPattern { // Get the network from the address and mask if netmask.is_empty() { - return Err(RuntimeException { - message: format!( - "Could not parse netmask {}", - String::from_utf8_lossy(&netmask) - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not parse netmask {}", + String::from_utf8_lossy(&netmask) + )) .into()); } if range_ip.is_empty() { - return Err(RuntimeException { - message: format!( - "Could not parse range IP {}", - String::from_utf8_lossy(range_ip) - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not parse range IP {}", + String::from_utf8_lossy(range_ip) + )) .into()); } diff --git a/crates/shirabe/src/util/perforce.rs b/crates/shirabe/src/util/perforce.rs index ac455fc7..d98f1edf 100644 --- a/crates/shirabe/src/util/perforce.rs +++ b/crates/shirabe/src/util/perforce.rs @@ -359,17 +359,13 @@ impl Perforce { if index.is_none() { return Ok(false); } - return Err(Exception { - message: format!("p4 command not found in path: {}", error_output), - code: 0, - } + return Err(Exception::new(format!( + "p4 command not found in path: {}", + error_output + )) .into()); } - return Err(Exception { - message: format!("Invalid user name: {}", user), - code: 0, - } - .into()); + return Err(Exception::new(format!("Invalid user name: {}", user)).into()); } Ok(true) @@ -497,11 +493,7 @@ impl Perforce { let spec = match fopen(&client_spec, "w") { Ok(spec) => spec, Err(e) => { - return Err(Exception { - message: e.to_string(), - code: 0, - } - .into()); + return Err(Exception::new(e.to_string()).into()); } }; let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { @@ -509,11 +501,7 @@ impl Perforce { })); if let Err(e) = result { fclose(&spec); - return Err(Exception { - message: format!("{:?}", e), - code: 0, - } - .into()); + return Err(Exception::new(format!("{:?}", e)).into()); } fclose(&spec); Ok(()) @@ -565,13 +553,10 @@ impl Perforce { process.run(None, indexmap::IndexMap::new())?; if !process.is_successful() { - return Err(Exception { - message: format!( - "Error logging in:{}", - self.process.borrow().get_error_output() - ), - code: 0, - } + return Err(Exception::new(format!( + "Error logging in:{}", + self.process.borrow().get_error_output() + )) .into()); } } diff --git a/crates/shirabe/src/util/platform.rs b/crates/shirabe/src/util/platform.rs index a71fea92..2bb5cf99 100644 --- a/crates/shirabe/src/util/platform.rs +++ b/crates/shirabe/src/util/platform.rs @@ -36,10 +36,9 @@ impl Platform { return Ok(String::new()); } - return Err(RuntimeException { - message: "Could not determine the current working directory".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "Could not determine the current working directory".to_string(), + ) .into()); } @@ -159,11 +158,7 @@ impl Platform { } } - Err(RuntimeException { - message: "Could not determine user directory".to_string(), - code: 0, - } - .into()) + Err(RuntimeException::new("Could not determine user directory".to_string()).into()) } /// @return bool Whether the host machine is running on the Windows Subsystem for Linux (WSL) diff --git a/crates/shirabe/src/util/process_executor.rs b/crates/shirabe/src/util/process_executor.rs index 463c5d19..b9373b6f 100644 --- a/crates/shirabe/src/util/process_executor.rs +++ b/crates/shirabe/src/util/process_executor.rs @@ -13,6 +13,7 @@ use shirabe_external_packages::symfony::process::Process; use shirabe_external_packages::symfony::process::ProcessMock; use shirabe_external_packages::symfony::process::exception::ProcessSignaledException; use shirabe_external_packages::symfony::process::exception::RuntimeException as SymfonyProcessRuntimeException; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ LogicException, PHP_EOL, PhpMixed, RuntimeException, array_intersect, array_map, escapeshellarg, explode, implode, in_array_strict, is_array, is_dir, is_numeric, is_string, @@ -253,17 +254,13 @@ impl ProcessExecutor { Some(Self::get_timeout() as f64), )?; } else { - return Err(LogicException { - message: "Invalid command type".to_string(), - code: 0, - } - .into()); + return Err(LogicException::new("Invalid command type".to_string()).into()); } if !Platform::is_windows() && tty { // PHP: try { $process->setTty(true); } catch (RuntimeException $e) { /* ignore */ } if let Err(e) = process.set_tty(true) - && e.downcast_ref::<SymfonyProcessRuntimeException>().is_none() + && !e.is_instanceof::<SymfonyProcessRuntimeException>() { return Err(e); } @@ -312,7 +309,7 @@ impl ProcessExecutor { let final_result: anyhow::Result<()> = match result { Ok(()) => Ok(()), Err(e) => { - if let Some(pse) = e.downcast_ref::<ProcessSignaledException>() { + if let Some(pse) = e.catch::<ProcessSignaledException>() { if signal_handler.is_triggered() { // exiting as we were signaled and the child process exited too due to the signal signal_handler.exit_with_last_signal(); @@ -451,21 +448,18 @@ impl ProcessExecutor { // strict-mode mismatch) extends `\RuntimeException`, so PHP call sites that // `catch (\RuntimeException $e)` around a mock-driven git/hg/svn call (e.g. // `GitDriver::supports`) treat a mismatch as an ordinary recoverable failure. Using - // the same `RuntimeException` type here keeps `downcast_ref::<RuntimeException>()` - // checks working the same way against a mismatch. - return Err(RuntimeException { - message: format!( - "Received unexpected command {:?} in \"{}\"{}{}{}Received calls:{}{}", - command, - cwd.unwrap_or(""), - PHP_EOL, - expected, - PHP_EOL, - PHP_EOL, - received - ), - code: 0, - } + // the same `RuntimeException` type here keeps `catch::<RuntimeException>()` checks + // working the same way against a mismatch. + return Err(RuntimeException::new(format!( + "Received unexpected command {:?} in \"{}\"{}{}{}Received calls:{}{}", + command, + cwd.unwrap_or(""), + PHP_EOL, + expected, + PHP_EOL, + PHP_EOL, + received + )) .into()); } @@ -645,10 +639,7 @@ impl ProcessExecutor { Box::pin(async move { if !allow_async { - return Err(LogicException { - message: "You must use the ProcessExecutor instance which is part of a Composer\\Loop instance to be able to run async processes".to_string(), - code: 0, - } + return Err(LogicException::new("You must use the ProcessExecutor instance which is part of a Composer\\Loop instance to be able to run async processes".to_string()) .into()); } @@ -682,11 +673,7 @@ impl ProcessExecutor { Some(Self::get_timeout() as f64), )? } else { - return Err(LogicException { - message: "Invalid command type".to_string(), - code: 0, - } - .into()); + return Err(LogicException::new("Invalid command type".to_string()).into()); }; process.start(None, IndexMap::new())?; diff --git a/crates/shirabe/src/util/remote_filesystem.rs b/crates/shirabe/src/util/remote_filesystem.rs index 2dafc0d3..e2901b2c 100644 --- a/crates/shirabe/src/util/remote_filesystem.rs +++ b/crates/shirabe/src/util/remote_filesystem.rs @@ -14,6 +14,7 @@ use crate::util::http::ProxyManager; use crate::util::http::Response; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ PHP_URL_HOST, PHP_URL_PATH, PHP_URL_SCHEME, PHP_VERSION_ID, PhpMixed, RuntimeException, STREAM_NOTIFY_FAILURE, STREAM_NOTIFY_FILE_SIZE_IS, STREAM_NOTIFY_PROGRESS, @@ -216,12 +217,11 @@ impl RemoteFilesystem { let mut file_url = file_url.to_string(); if options.contains_key("prevent_ip_access_callable") { - return Err(anyhow::anyhow!(RuntimeException { - message: - "RemoteFilesystem doesn't support the 'prevent_ip_access_callable' config." - .to_string(), - code: 0, - })); + return Err(RuntimeException::new( + "RemoteFilesystem doesn't support the 'prevent_ip_access_callable' config." + .to_string(), + ) + .into()); } if let Some(token) = options.get("gitlab-token").cloned() { @@ -404,7 +404,7 @@ impl RemoteFilesystem { })(); let mut caught_e: Option<anyhow::Error> = None; if let Err(mut e) = inner_result { - if let Some(te) = e.downcast_mut::<TransportException>() { + if let Some(te) = e.catch_mut::<TransportException>() { if !http_response_header.is_empty() && !http_response_header[0].is_empty() { te.set_headers(http_response_header.clone()); te.set_status_code(Self::find_status_code(&http_response_header)); @@ -535,7 +535,7 @@ impl RemoteFilesystem { ); } - let mut e = TransportException::new_with_code( + let mut e = TransportException::new( format!( "The \"{}\" file could not be downloaded ({})", self.file_url, http_response_header[0] @@ -607,13 +607,14 @@ impl RemoteFilesystem { if result.is_some() && file_name.is_some() && !is_redirect { let result_str = result.as_deref().unwrap(); if result_str.is_empty() { - return Err(anyhow::anyhow!(TransportException::new( + return Err(TransportException::new( format!( "\"{}\" appears broken, and returned an empty 200 response", self.file_url ), 0, - ))); + ) + .into()); } // TODO(phase-c): PHP captures the file_put_contents warning here via set_error_handler @@ -623,7 +624,7 @@ impl RemoteFilesystem { let write_result = file_put_contents(file_name.as_deref().unwrap(), result_str.as_bytes()); if write_result.is_none() { - return Err(anyhow::anyhow!(TransportException::new( + return Err(TransportException::new( format!( "The \"{}\" file could not be written to {}: {}", self.file_url, @@ -631,7 +632,8 @@ impl RemoteFilesystem { put_error_message ), 0, - ))); + ) + .into()); } let _ = put_error_message; } @@ -659,7 +661,7 @@ impl RemoteFilesystem { } if result.is_none() { - let mut e = TransportException::new_with_code( + let mut e = TransportException::new( format!( "The \"{}\" file could not be downloaded: {}", self.file_url, error_message @@ -750,11 +752,12 @@ impl RemoteFilesystem { && let Some(max) = max_file_size && Platform::strlen(r) >= max { - return Err(anyhow::anyhow!(MaxFileSizeExceededException::new(format!( + return Err(MaxFileSizeExceededException::new(format!( "Maximum allowed download size reached. Downloaded {} of allowed {} bytes", Platform::strlen(r), max - )))); + )) + .into()); } if PHP_VERSION_ID >= 80400 { @@ -785,14 +788,15 @@ impl RemoteFilesystem { match notification_code { x if x == STREAM_NOTIFY_FAILURE => { if 400 == message_code { - return Err(anyhow::anyhow!(TransportException::new_with_code( + return Err(TransportException::new( format!( "The '{}' URL could not be accessed: {}", self.file_url, message.unwrap_or_default() ), message_code, - ))); + ) + .into()); } } x if x == STREAM_NOTIFY_FILE_SIZE_IS => { @@ -848,10 +852,7 @@ impl RemoteFilesystem { self.retry = result.retry; if self.retry { - return Err(anyhow::anyhow!(TransportException::new( - "RETRY".to_string(), - 0, - ))); + return Err(TransportException::new("RETRY".to_string(), 0).into()); } Ok(()) } @@ -1043,10 +1044,11 @@ impl RemoteFilesystem { // RemoteFilesystem as a String; from_utf8_lossy can corrupt binary payloads Some(d) => Some(String::from_utf8_lossy(&d).into_owned()), None => { - return Err(anyhow::anyhow!(TransportException::new( + return Err(TransportException::new( "Failed to decode zlib stream".to_string(), 0, - ))); + ) + .into()); } }; } diff --git a/crates/shirabe/src/util/stream_context_factory.rs b/crates/shirabe/src/util/stream_context_factory.rs index cae00c08..b407e897 100644 --- a/crates/shirabe/src/util/stream_context_factory.rs +++ b/crates/shirabe/src/util/stream_context_factory.rs @@ -22,7 +22,7 @@ impl StreamContextFactory { url: &str, default_options: IndexMap<String, PhpMixed>, default_params: IndexMap<String, PhpMixed>, - ) -> anyhow::Result<PhpMixed, TransportException> { + ) -> anyhow::Result<PhpMixed, Box<TransportException>> { let mut options: IndexMap<String, PhpMixed> = { let mut http = IndexMap::new(); // specify defaults again to try and work better with curlwrappers enabled @@ -66,7 +66,7 @@ impl StreamContextFactory { url: &str, mut options: IndexMap<String, PhpMixed>, for_curl: bool, - ) -> anyhow::Result<IndexMap<String, PhpMixed>, TransportException> { + ) -> anyhow::Result<IndexMap<String, PhpMixed>, Box<TransportException>> { // Make sure the headers are in an array form let has_header = options .get("http") @@ -105,23 +105,23 @@ impl StreamContextFactory { if proxy.is_secure() { if !extension_loaded("openssl") { - return Err(TransportException::new( + return Err(Box::new(TransportException::new( "You must enable the openssl extension to use a secure proxy." .to_string(), 0, - )); + ))); } if is_https_request { - return Err(TransportException::new( + return Err(Box::new(TransportException::new( "You must enable the curl extension to make https requests through a secure proxy.".to_string(), 0, - )); + ))); } } else if is_https_request && !extension_loaded("openssl") { - return Err(TransportException::new( + return Err(Box::new(TransportException::new( "You must enable the openssl extension to make https requests through a proxy.".to_string(), 0, - )); + ))); } // Header will be a Proxy-Authorization string or not set @@ -224,7 +224,7 @@ impl StreamContextFactory { // `logger` was a PSR LoggerInterface; CaBundle is slated for removal so // it is now an unused `()` placeholder. logger: (), - ) -> anyhow::Result<IndexMap<String, PhpMixed>, TransportException> { + ) -> anyhow::Result<IndexMap<String, PhpMixed>, Box<TransportException>> { let ciphers = [ "ECDHE-RSA-AES128-GCM-SHA256", "ECDHE-ECDSA-AES128-GCM-SHA256", @@ -336,10 +336,10 @@ impl StreamContextFactory { if let Some(ref cafile) = cafile && (!Filesystem::is_readable(cafile) || !CaBundle::validate_ca_file(cafile, logger)) { - return Err(TransportException::new( + return Err(Box::new(TransportException::new( "The configured cafile was not valid or could not be read.".to_string(), 0, - )); + ))); } let capath = defaults @@ -351,10 +351,10 @@ impl StreamContextFactory { if let Some(ref capath) = capath && (!shirabe_php_shim::is_dir(capath) || !Filesystem::is_readable(capath)) { - return Err(TransportException::new( + return Err(Box::new(TransportException::new( "The configured capath was not valid or could not be read.".to_string(), 0, - )); + ))); } // Disable TLS compression to prevent CRIME attacks where supported. diff --git a/crates/shirabe/src/util/svn.rs b/crates/shirabe/src/util/svn.rs index a2974760..aaa0c6d1 100644 --- a/crates/shirabe/src/util/svn.rs +++ b/crates/shirabe/src/util/svn.rs @@ -177,11 +177,7 @@ impl Svn { && stripos(&full_output, "svn: E170001:").is_none() && stripos(&full_output, "svn: E215004:").is_none() { - return Err(RuntimeException { - message: full_output, - code: 0, - } - .into()); + return Err(RuntimeException::new(full_output).into()); } if !self.has_auth() { @@ -196,11 +192,7 @@ impl Svn { return self.execute_with_auth_retry(svn_command, cwd, url, path, verbose); } - Err(RuntimeException { - message: format!("wrong credentials provided ({})", full_output), - code: 0, - } - .into()) + Err(RuntimeException::new(format!("wrong credentials provided ({})", full_output)).into()) } pub fn set_cache_credentials(&mut self, cache_credentials: bool) { @@ -213,10 +205,9 @@ impl Svn { pub(crate) fn do_auth_dance(&mut self) -> anyhow::Result<&mut Self> { // cannot ask for credentials in non interactive mode if !self.io.is_interactive() { - return Err(RuntimeException { - message: "can not ask for authentication in non interactive mode".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "can not ask for authentication in non interactive mode".to_string(), + ) .into()); } @@ -310,11 +301,7 @@ impl Svn { /// @throws \LogicException pub(crate) fn get_password(&self) -> anyhow::Result<String> { if self.credentials.is_none() { - return Err(LogicException { - message: "No svn auth detected.".to_string(), - code: 0, - } - .into()); + return Err(LogicException::new("No svn auth detected.".to_string()).into()); } Ok(self.credentials.as_ref().unwrap().password.clone()) @@ -325,11 +312,7 @@ impl Svn { /// @throws \LogicException pub(crate) fn get_username(&self) -> anyhow::Result<String> { if self.credentials.is_none() { - return Err(LogicException { - message: "No svn auth detected.".to_string(), - code: 0, - } - .into()); + return Err(LogicException::new("No svn auth detected.".to_string()).into()); } Ok(self.credentials.as_ref().unwrap().username.clone()) diff --git a/crates/shirabe/src/util/tar.rs b/crates/shirabe/src/util/tar.rs index f5b0ebbe..94320edb 100644 --- a/crates/shirabe/src/util/tar.rs +++ b/crates/shirabe/src/util/tar.rs @@ -20,10 +20,8 @@ impl Tar { /// UTF-8 could never survive the JSON parsing that follows in PHP either. fn content_to_string(content: Vec<u8>) -> anyhow::Result<String> { String::from_utf8(content).map_err(|_| { - anyhow::anyhow!(RuntimeException { - message: "composer.json in the archive is not valid UTF-8".to_string(), - code: 0, - }) + RuntimeException::new("composer.json in the archive is not valid UTF-8".to_string()) + .into() }) } @@ -38,17 +36,14 @@ impl Tar { if folder_file.is_dir() { top_level_paths.insert(name, true); if top_level_paths.len() > 1 { - return Err(anyhow::anyhow!(RuntimeException { - message: format!( - "Archive has more than one top level directories, and no composer.json was found on the top level, so it's an invalid archive. Top level paths found were: {}", - top_level_paths - .keys() - .cloned() - .collect::<Vec<_>>() - .join(",") - ), - code: 0, - })); + return Err(RuntimeException::new(format!( + "Archive has more than one top level directories, and no composer.json was found on the top level, so it's an invalid archive. Top level paths found were: {}", + top_level_paths + .keys() + .cloned() + .collect::<Vec<_>>() + .join(",") + )).into()); } } } @@ -63,11 +58,10 @@ impl Tar { return Self::content_to_string(file.get_content()); } - Err(anyhow::anyhow!(RuntimeException { - message: - "No composer.json found either at the top level or within the topmost directory" - .to_string(), - code: 0, - })) + Err(RuntimeException::new( + "No composer.json found either at the top level or within the topmost directory" + .to_string(), + ) + .into()) } } diff --git a/crates/shirabe/src/util/zip.rs b/crates/shirabe/src/util/zip.rs index a671d63b..0ba177d0 100644 --- a/crates/shirabe/src/util/zip.rs +++ b/crates/shirabe/src/util/zip.rs @@ -10,10 +10,9 @@ pub struct Zip; impl Zip { pub fn get_composer_json(path_to_zip: &str) -> anyhow::Result<Option<String>> { if !extension_loaded("zip") { - return Err(RuntimeException { - message: "The Zip Util requires PHP's zip extension".to_string(), - code: 0, - } + return Err(RuntimeException::new( + "The Zip Util requires PHP's zip extension".to_string(), + ) .into()); } @@ -64,13 +63,10 @@ impl Zip { if dir_name == "." { top_level_paths.insert(name, true); if top_level_paths.len() > 1 { - return Err(RuntimeException { - message: format!( - "Archive has more than one top level directories, and no composer.json was found on the top level, so it's an invalid archive. Top level paths found were: {}", - implode(",", &top_level_paths.keys().cloned().collect::<Vec<_>>()) - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Archive has more than one top level directories, and no composer.json was found on the top level, so it's an invalid archive. Top level paths found were: {}", + implode(",", &top_level_paths.keys().cloned().collect::<Vec<_>>()) + )) .into()); } continue; @@ -80,13 +76,10 @@ impl Zip { if !dir_name.contains('\\') && !dir_name.contains('/') { top_level_paths.insert(format!("{}/", dir_name), true); if top_level_paths.len() > 1 { - return Err(RuntimeException { - message: format!( - "Archive has more than one top level directories, and no composer.json was found on the top level, so it's an invalid archive. Top level paths found were: {}", - implode(",", &top_level_paths.keys().cloned().collect::<Vec<_>>()) - ), - code: 0, - } + return Err(RuntimeException::new(format!( + "Archive has more than one top level directories, and no composer.json was found on the top level, so it's an invalid archive. Top level paths found were: {}", + implode(",", &top_level_paths.keys().cloned().collect::<Vec<_>>()) + )) .into()); } } @@ -101,12 +94,10 @@ impl Zip { } } - Err(RuntimeException { - message: - "No composer.json found either at the top level or within the topmost directory" - .to_string(), - code: 0, - } + Err(RuntimeException::new( + "No composer.json found either at the top level or within the topmost directory" + .to_string(), + ) .into()) } } diff --git a/crates/shirabe/tests/downloader/download_manager_test.rs b/crates/shirabe/tests/downloader/download_manager_test.rs index 9d12bad6..dd894565 100644 --- a/crates/shirabe/tests/downloader/download_manager_test.rs +++ b/crates/shirabe/tests/downloader/download_manager_test.rs @@ -253,13 +253,7 @@ fn test_full_package_download_failover() { .expect_download() .times(1) .withf(|_pkg, path, _prev, _output| path == "target_dir") - .returning(|_, _, _, _| { - Err(RuntimeException { - message: "Foo".to_string(), - code: 0, - } - .into()) - }); + .returning(|_, _, _, _| Err(RuntimeException::new("Foo".to_string()).into())); let mut downloader_success = downloader_mock("source"); downloader_success diff --git a/crates/shirabe/tests/downloader/file_downloader_test.rs b/crates/shirabe/tests/downloader/file_downloader_test.rs index 469bf473..eac4e45e 100644 --- a/crates/shirabe/tests/downloader/file_downloader_test.rs +++ b/crates/shirabe/tests/downloader/file_downloader_test.rs @@ -17,6 +17,7 @@ use shirabe::util::HttpDownloader; use shirabe::util::filesystem::{Filesystem, FilesystemMock}; use shirabe::util::http_downloader::HttpDownloaderMockHandler; use shirabe::util::r#loop::Loop; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ InvalidArgumentException, PhpMixed, RuntimeException, UnexpectedValueException, }; @@ -84,7 +85,7 @@ fn test_download_for_package_without_dist_reference() { let e = result.expect_err("expected InvalidArgumentException"); assert!( - e.downcast_ref::<InvalidArgumentException>().is_some(), + e.is_instanceof::<InvalidArgumentException>(), "expected InvalidArgumentException, got: {e}" ); } @@ -107,7 +108,7 @@ fn test_download_to_existing_file() { let e = result.expect_err("download to an existing file was expected to throw"); assert!( - e.downcast_ref::<RuntimeException>().is_some(), + e.is_instanceof::<RuntimeException>(), "expected RuntimeException, got: {e}" ); assert!( @@ -167,7 +168,7 @@ fn test_download_but_file_is_unsaved() { let e = result.expect_err("download was expected to throw"); assert!( - e.downcast_ref::<UnexpectedValueException>().is_some(), + e.is_instanceof::<UnexpectedValueException>(), "expected UnexpectedValueException, got: {e}" ); assert!( @@ -294,7 +295,7 @@ fn test_download_file_with_invalid_checksum() { let e = result.expect_err("download was expected to throw"); assert!( - e.downcast_ref::<UnexpectedValueException>().is_some(), + e.is_instanceof::<UnexpectedValueException>(), "expected UnexpectedValueException, got: {e}" ); assert!( diff --git a/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs b/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs index af8a33db..817a6954 100644 --- a/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs +++ b/crates/shirabe/tests/event_dispatcher/event_dispatcher_test.rs @@ -27,6 +27,7 @@ use shirabe::util::platform::Platform; use shirabe::util::process_executor::{MockHandler, ProcessExecutor}; use shirabe_class_map_generator::class_map::ClassMap; use shirabe_external_packages::symfony::console::output::output_interface; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{PHP_EOL, PhpMixed}; fn tear_down() { @@ -286,8 +287,7 @@ fn test_dispatcher_detect_infinite_recursion() { let result = dispatcher.dispatch(Some("root"), Some(&mut event)); let err = result.expect_err("infinite recursion must raise a RuntimeException"); assert!( - err.downcast_ref::<shirabe_php_shim::RuntimeException>() - .is_some(), + err.is_instanceof::<shirabe_php_shim::RuntimeException>(), "expected RuntimeException, got: {err:?}" ); } @@ -392,8 +392,7 @@ fn test_listener_exceptions_are_caught() { let e = result.expect_err("expected RuntimeException"); assert!( - e.downcast_ref::<shirabe_php_shim::RuntimeException>() - .is_some(), + e.is_instanceof::<shirabe_php_shim::RuntimeException>(), "got: {e:?}" ); } diff --git a/crates/shirabe/tests/json/composer_schema_test.rs b/crates/shirabe/tests/json/composer_schema_test.rs index 9cbea150..f2a5da44 100644 --- a/crates/shirabe/tests/json/composer_schema_test.rs +++ b/crates/shirabe/tests/json/composer_schema_test.rs @@ -9,6 +9,7 @@ //! which property) is identical to upstream. use shirabe::json::{JsonFile, JsonValidationException}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::json_decode; const NAME_PATTERN: &str = r#"^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$"#; @@ -22,7 +23,7 @@ fn check(json: &str) -> Vec<String> { match JsonFile::validate_json_schema("test", &data, JsonFile::LAX_SCHEMA, None) { Ok(_) => Vec::new(), Err(e) => e - .downcast_ref::<JsonValidationException>() + .catch::<JsonValidationException>() .unwrap() .get_errors() .clone(), diff --git a/crates/shirabe/tests/json/json_file_test.rs b/crates/shirabe/tests/json/json_file_test.rs index 49ac1605..a20e7234 100644 --- a/crates/shirabe/tests/json/json_file_test.rs +++ b/crates/shirabe/tests/json/json_file_test.rs @@ -3,6 +3,7 @@ use indexmap::IndexMap; use shirabe::json::{JsonEncodeOptions, JsonFile, JsonValidationException}; use shirabe_external_packages::seld::json_lint::ParsingException; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::PhpMixed; /// ref: JsonFileTest::expectParseException @@ -269,14 +270,14 @@ fn test_schema_validation_error() { let err = json .validate_schema(JsonFile::STRICT_SCHEMA, None) .unwrap_err(); - let e = err.downcast_ref::<JsonValidationException>().unwrap(); + let e = err.catch::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); assert!(e.get_errors().contains(&expected_error)); let err = json .validate_schema(JsonFile::LAX_SCHEMA, None) .unwrap_err(); - let e = err.downcast_ref::<JsonValidationException>().unwrap(); + let e = err.catch::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); assert!(e.get_errors().contains(&expected_error)); } @@ -298,7 +299,7 @@ fn test_schema_validation_lax_additional_properties() { let err = json .validate_schema(JsonFile::STRICT_SCHEMA, None) .unwrap_err(); - let e = err.downcast_ref::<JsonValidationException>().unwrap(); + let e = err.catch::<JsonValidationException>().unwrap(); assert_eq!( format!("\"{}\" does not match the expected JSON schema", file), e.get_message() @@ -327,7 +328,7 @@ fn test_schema_validation_lax_required() { let err = json .validate_schema(JsonFile::STRICT_SCHEMA, None) .unwrap_err(); - let e = err.downcast_ref::<JsonValidationException>().unwrap(); + let e = err.catch::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); let errors = e.get_errors(); assert!(errors.contains(&"name : \"name\" is a required property".to_string())); @@ -338,7 +339,7 @@ fn test_schema_validation_lax_required() { let err = json .validate_schema(JsonFile::STRICT_SCHEMA, None) .unwrap_err(); - let e = err.downcast_ref::<JsonValidationException>().unwrap(); + let e = err.catch::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); assert_eq!( &vec!["description : \"description\" is a required property".to_string()], @@ -350,7 +351,7 @@ fn test_schema_validation_lax_required() { let err = json .validate_schema(JsonFile::STRICT_SCHEMA, None) .unwrap_err(); - let e = err.downcast_ref::<JsonValidationException>().unwrap(); + let e = err.catch::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); assert_eq!( &vec!["name : \"name\" is a required property".to_string()], @@ -362,7 +363,7 @@ fn test_schema_validation_lax_required() { let err = json .validate_schema(JsonFile::STRICT_SCHEMA, None) .unwrap_err(); - let e = err.downcast_ref::<JsonValidationException>().unwrap(); + let e = err.catch::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); let errors = e.get_errors(); assert!(errors.contains(&"name : \"name\" is a required property".to_string())); @@ -373,7 +374,7 @@ fn test_schema_validation_lax_required() { let err = json .validate_schema(JsonFile::STRICT_SCHEMA, None) .unwrap_err(); - let e = err.downcast_ref::<JsonValidationException>().unwrap(); + let e = err.catch::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); let errors = e.get_errors(); assert!(errors.contains(&"name : \"name\" is a required property".to_string())); @@ -444,7 +445,7 @@ fn test_auth_schema_validation_with_custom_data_source() { let err = JsonFile::validate_json_schema("COMPOSER_AUTH", &json, JsonFile::AUTH_SCHEMA, None) .unwrap_err(); - let e = err.downcast_ref::<JsonValidationException>().unwrap(); + let e = err.catch::<JsonValidationException>().unwrap(); assert_eq!(expected_message, e.get_message()); assert_eq!(&vec![expected_error], e.get_errors()); } @@ -519,7 +520,7 @@ fn test_composer_lock_file_merge_conflict_complex() { std::fs::read_to_string(fixture_path("composer-lock-merge-conflict-complex.txt")).unwrap(); let err = JsonFile::parse_json(Some(&data), Some("/path/to/composer.lock")).unwrap_err(); - assert!(err.downcast_ref::<ParsingException>().is_some()); + assert!(err.is_instanceof::<ParsingException>()); } #[test] @@ -531,7 +532,7 @@ fn test_composer_lock_file_merge_conflict_complex_crlf() { .unwrap(); let err = JsonFile::parse_json(Some(&data), Some("/path/to/composer.lock")).unwrap_err(); - assert!(err.downcast_ref::<ParsingException>().is_some()); + assert!(err.is_instanceof::<ParsingException>()); } #[test] diff --git a/crates/shirabe/tests/package/archiver/archive_manager_test.rs b/crates/shirabe/tests/package/archiver/archive_manager_test.rs index a7235bf9..84feb5bc 100644 --- a/crates/shirabe/tests/package/archiver/archive_manager_test.rs +++ b/crates/shirabe/tests/package/archiver/archive_manager_test.rs @@ -13,6 +13,7 @@ use shirabe::util::ProcessExecutor; use shirabe::util::http_downloader::HttpDownloader; use shirabe::util::r#loop::Loop; use shirabe_external_packages::symfony::process::Process; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ PhpMixed, file_exists, file_put_contents, realpath, sys_get_temp_dir, unlink, }; @@ -159,10 +160,7 @@ fn test_unknown_format() { ); let err = result.expect_err("expected RuntimeException for unknown format"); - assert!( - err.downcast_ref::<shirabe_php_shim::RuntimeException>() - .is_some() - ); + assert!(err.is_instanceof::<shirabe_php_shim::RuntimeException>()); } // ref: ArchiveManagerTest::testArchiveTar / testArchiveCustomFileName. diff --git a/crates/shirabe/tests/package/loader/validating_array_loader_test.rs b/crates/shirabe/tests/package/loader/validating_array_loader_test.rs index 07f3e4a6..1b1ed448 100644 --- a/crates/shirabe/tests/package/loader/validating_array_loader_test.rs +++ b/crates/shirabe/tests/package/loader/validating_array_loader_test.rs @@ -4,6 +4,7 @@ use crate::test_case; use indexmap::IndexMap; use shirabe::package::handle::PackageInterfaceHandle; use shirabe::package::loader::{InvalidPackageException, LoaderInterface, ValidatingArrayLoader}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::PhpMixed; fn s(v: &str) -> PhpMixed { @@ -792,7 +793,7 @@ fn test_load_failure_throws_exception() { Ok(_) => panic!("Expected exception to be thrown"), Err(e) => { let exception = e - .downcast_ref::<InvalidPackageException>() + .catch::<InvalidPackageException>() .expect("Expected InvalidPackageException"); let mut errors: Vec<String> = exception.get_errors().to_vec(); expected_errors.sort(); diff --git a/crates/shirabe/tests/package/locker_test.rs b/crates/shirabe/tests/package/locker_test.rs index f1747166..4732d679 100644 --- a/crates/shirabe/tests/package/locker_test.rs +++ b/crates/shirabe/tests/package/locker_test.rs @@ -10,6 +10,7 @@ use shirabe::package::handle::{CompletePackageHandle, PackageInterfaceHandle}; use shirabe::plugin::plugin_interface; use shirabe::repository::{FindPackageConstraint, RepositoryInterfaceHandle}; use shirabe::util::process_executor::ProcessExecutor; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{LogicException, PhpMixed, hash}; use tempfile::TempDir; @@ -83,7 +84,7 @@ fn test_get_not_locked_packages() { .get_locked_repository(false) .expect_err("getLockedRepository should fail when no lock file exists"); assert!( - err.downcast_ref::<LogicException>().is_some(), + err.is_instanceof::<LogicException>(), "expected LogicException, got: {err}" ); } @@ -219,7 +220,7 @@ fn test_lock_bad_packages() { ) .expect_err("setLockData should fail for a package with no version"); assert!( - err.downcast_ref::<LogicException>().is_some(), + err.is_instanceof::<LogicException>(), "expected LogicException, got: {err}" ); } diff --git a/crates/shirabe/tests/plugin/plugin_installer_test.rs b/crates/shirabe/tests/plugin/plugin_installer_test.rs index 06cd2ce9..0c82a11a 100644 --- a/crates/shirabe/tests/plugin/plugin_installer_test.rs +++ b/crates/shirabe/tests/plugin/plugin_installer_test.rs @@ -31,6 +31,7 @@ use shirabe::util::r#loop::Loop; use shirabe::util::process_executor::ProcessExecutor; use shirabe_external_packages::symfony::console::output::output_interface::VERBOSITY_NORMAL; use shirabe_external_packages::symfony::process::PhpExecutableFinder; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::PhpMixed; use shirabe_semver::VersionParser; use tempfile::TempDir; @@ -859,8 +860,7 @@ fn test_querying_with_invalid_capability_class_name_throws() { ), }; assert!( - err.downcast_ref::<shirabe_php_shim::UnexpectedValueException>() - .is_some(), + err.is_instanceof::<shirabe_php_shim::UnexpectedValueException>(), "expected UnexpectedValueException for {invalid_implementation_class_name:?}, got: {err}" ); // PHP: ->expects($this->once())->method('getCapabilities'). diff --git a/crates/shirabe/tests/repository/filesystem_repository_test.rs b/crates/shirabe/tests/repository/filesystem_repository_test.rs index 3279f29e..a9ac91a0 100644 --- a/crates/shirabe/tests/repository/filesystem_repository_test.rs +++ b/crates/shirabe/tests/repository/filesystem_repository_test.rs @@ -13,6 +13,7 @@ use shirabe::package::{Link, PackageInterfaceHandle, RootAliasPackageHandle, Roo use shirabe::repository::RepositoryInterface; use shirabe::repository::filesystem_repository::FilesystemRepository; use shirabe::util::filesystem::Filesystem; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::PhpMixed; use shirabe_semver::VersionParser; @@ -50,7 +51,6 @@ fn test_repository_read() { assert_eq!(packages[0].get_type(), "vendor"); } -#[ignore = "InvalidRepositoryException message building calls shirabe_php_shim::var::get_class_err(), which is still todo!()"] #[test] fn test_corrupted_repository_file() { // PHP mocks read() to return the scalar string 'foo'; a real file containing the JSON string @@ -63,7 +63,7 @@ fn test_corrupted_repository_file() { let result = repository.get_packages(); let err = result.unwrap_err(); assert!( - err.is::<shirabe::repository::InvalidRepositoryException>(), + err.is_instanceof::<shirabe::repository::InvalidRepositoryException>(), "expected InvalidRepositoryException, got: {err}" ); } diff --git a/crates/shirabe/tests/repository/vcs/git_bitbucket_driver_test.rs b/crates/shirabe/tests/repository/vcs/git_bitbucket_driver_test.rs index 4d84bcf2..67e9c8a9 100644 --- a/crates/shirabe/tests/repository/vcs/git_bitbucket_driver_test.rs +++ b/crates/shirabe/tests/repository/vcs/git_bitbucket_driver_test.rs @@ -10,6 +10,7 @@ use shirabe::repository::vcs::GitBitbucketDriver; use shirabe::util::filesystem::Filesystem; use shirabe::util::http_downloader::{HttpDownloader, HttpDownloaderMockHandler}; use shirabe::util::process_executor::ProcessExecutor; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{InvalidArgumentException, PhpMixed, RuntimeException}; use tempfile::TempDir; @@ -112,11 +113,11 @@ fn test_get_root_identifier_wrong_scm_type() { let err = driver.get_root_identifier().unwrap_err(); let runtime = err - .downcast_ref::<RuntimeException>() + .catch::<RuntimeException>() .expect("expected RuntimeException"); assert_eq!( "https://bitbucket.org/user/repo.git does not appear to be a git repository, use https://bitbucket.org/user/repo but remember that Bitbucket no longer supports the mercurial repositories. https://bitbucket.org/blog/sunsetting-mercurial-support-in-bitbucket", - runtime.message + runtime.get_message() ); } @@ -250,7 +251,7 @@ fn test_initialize_invalid_repository_url() { let result = get_driver("https://bitbucket.org/acme", io, config, http_downloader); let err = result.unwrap_err(); assert!( - err.downcast_ref::<InvalidArgumentException>().is_some(), + err.is_instanceof::<InvalidArgumentException>(), "expected InvalidArgumentException, got: {err:?}" ); } diff --git a/crates/shirabe/tests/repository/vcs/git_driver_test.rs b/crates/shirabe/tests/repository/vcs/git_driver_test.rs index 7a85d4cc..8494c1ca 100644 --- a/crates/shirabe/tests/repository/vcs/git_driver_test.rs +++ b/crates/shirabe/tests/repository/vcs/git_driver_test.rs @@ -12,6 +12,7 @@ use shirabe::util::filesystem::Filesystem; use shirabe::util::http_downloader::HttpDownloaderMockHandler; use shirabe::util::platform::Platform; use shirabe::util::process_executor::MockHandler; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{PhpMixed, RuntimeException}; use tempfile::TempDir; @@ -293,7 +294,7 @@ fn test_file_get_content_invalid_identifier() { assert_eq!(None, driver.get_file_content("file.txt", "h").unwrap()); let err = driver.get_file_content("file.txt", "-h").unwrap_err(); - assert!(err.downcast_ref::<RuntimeException>().is_some()); + assert!(err.is_instanceof::<RuntimeException>()); } #[test] @@ -324,5 +325,5 @@ fn test_get_change_date_invalid_identifier() { let mut driver = GitDriver::new(repo_config, io, config, http_downloader, process); let err = driver.get_change_date("-n1 --format=%at HEAD").unwrap_err(); - assert!(err.downcast_ref::<RuntimeException>().is_some()); + assert!(err.is_instanceof::<RuntimeException>()); } diff --git a/crates/shirabe/tests/repository/vcs/hg_driver_test.rs b/crates/shirabe/tests/repository/vcs/hg_driver_test.rs index be36f412..4245e140 100644 --- a/crates/shirabe/tests/repository/vcs/hg_driver_test.rs +++ b/crates/shirabe/tests/repository/vcs/hg_driver_test.rs @@ -11,6 +11,7 @@ use shirabe::repository::vcs::HgDriver; use shirabe::util::filesystem::Filesystem; use shirabe::util::http_downloader::HttpDownloaderMockHandler; use shirabe::util::process_executor::MockHandler; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{PhpMixed, RuntimeException}; use tempfile::TempDir; @@ -141,7 +142,7 @@ fn test_file_get_content_invalid_identifier() { assert_eq!(None, driver.get_file_content("file.txt", "h").unwrap()); let err = driver.get_file_content("file.txt", "-h").unwrap_err(); - assert!(err.downcast_ref::<RuntimeException>().is_some()); + assert!(err.is_instanceof::<RuntimeException>()); } #[test] @@ -168,5 +169,5 @@ fn test_get_change_date_invalid_identifier() { let driver = HgDriver::new(repo_config, io, config, http_downloader, process); let err = driver.get_change_date("-r foo").unwrap_err(); - assert!(err.downcast_ref::<RuntimeException>().is_some()); + assert!(err.is_instanceof::<RuntimeException>()); } diff --git a/crates/shirabe/tests/repository/vcs/svn_driver_test.rs b/crates/shirabe/tests/repository/vcs/svn_driver_test.rs index 6a74063d..bc45b082 100644 --- a/crates/shirabe/tests/repository/vcs/svn_driver_test.rs +++ b/crates/shirabe/tests/repository/vcs/svn_driver_test.rs @@ -11,6 +11,7 @@ use shirabe::repository::vcs::SvnDriver; use shirabe::util::filesystem::Filesystem; use shirabe::util::http_downloader::HttpDownloaderMockHandler; use shirabe::util::process_executor::MockHandler; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{PhpMixed, RuntimeException}; use tempfile::TempDir; @@ -130,10 +131,10 @@ fn test_wrong_credentials_in_url() { let mut svn = SvnDriver::new(repo_config, console, config, http_downloader, process); let err = svn.initialize().unwrap_err(); let runtime = err - .downcast_ref::<RuntimeException>() + .catch::<RuntimeException>() .expect("expected RuntimeException"); assert_eq!( "Repository https://till:secret@corp.svn.local/repo could not be processed, wrong credentials provided (svn: OPTIONS of 'https://corp.svn.local/repo': authorization failed: Could not authenticate to server: rejected Basic challenge (https://corp.svn.local/))", - runtime.message + runtime.get_message() ); } diff --git a/crates/shirabe/tests/util/auth_helper_test.rs b/crates/shirabe/tests/util/auth_helper_test.rs index ef5daaf7..6b376f97 100644 --- a/crates/shirabe/tests/util/auth_helper_test.rs +++ b/crates/shirabe/tests/util/auth_helper_test.rs @@ -7,6 +7,7 @@ use shirabe::config::ConfigSourceInterface; use shirabe::io::IOInterface; use shirabe::io::io_interface; use shirabe::util::{AuthHelper, Bitbucket, StoreAuth}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{PhpMixed, base64_encode, json_encode}; // Mirrors AuthHelperTest::setUp: a DEBUG-verbosity IOMock plus a real Config, both @@ -560,7 +561,7 @@ fn test_store_auth_with_prompt_invalid_answer() { let err = auth_helper .store_auth(origin, StoreAuth::Prompt) .expect_err("expected a RuntimeException"); - assert!(err.downcast_ref::<RuntimeException>().is_some()); + assert!(err.is_instanceof::<RuntimeException>()); // Mirrors PHP's `->with('Do you want to store credentials for '.$origin.' in '. // $configSourceName.' ? [Yn] ', $this->anything(), null, 'y')` verification on askAndValidate. @@ -639,7 +640,7 @@ fn test_prompt_auth_if_needed_git_lab_no_auth_change() { ); let err = result.expect_err("expected a TransportException"); - assert!(err.downcast_ref::<TransportException>().is_some()); + assert!(err.is_instanceof::<TransportException>()); assert_eq!( vec![( diff --git a/crates/shirabe/tests/util/git_test.rs b/crates/shirabe/tests/util/git_test.rs index 907ec38a..dbc06137 100644 --- a/crates/shirabe/tests/util/git_test.rs +++ b/crates/shirabe/tests/util/git_test.rs @@ -11,6 +11,7 @@ use shirabe::util::filesystem::Filesystem; use shirabe::util::git::Git; use shirabe::util::http_downloader::HttpDownloaderMockHandler; use shirabe::util::process_executor::{MockExpectation, MockHandler, ProcessExecutor}; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{PhpMixed, RuntimeException}; // No-op ConfigSourceInterface, equivalent to PHPUnit's @@ -195,7 +196,7 @@ fn test_run_command_private_git_hub_repository_not_initial_clone_not_interactive ); let err = result.expect_err("expected a RuntimeException"); - assert!(err.downcast_ref::<RuntimeException>().is_some()); + assert!(err.is_instanceof::<RuntimeException>()); } // privateGithubWithCredentialsProvider helper. diff --git a/crates/shirabe/tests/util/http_downloader_test.rs b/crates/shirabe/tests/util/http_downloader_test.rs index b438dca9..f1883721 100644 --- a/crates/shirabe/tests/util/http_downloader_test.rs +++ b/crates/shirabe/tests/util/http_downloader_test.rs @@ -11,6 +11,7 @@ use shirabe::io::io_interface; use shirabe::util::Platform; use shirabe::util::http_downloader::HttpDownloader; use shirabe_external_packages::symfony::console::output::output_interface::VERBOSITY_NORMAL; +use shirabe_php_shim::Catch as _; use shirabe_php_shim::{PHP_EOL, PhpMixed}; // PHP performs a live HTTP get to assert the URL's user:pass is captured via @@ -49,7 +50,7 @@ fn test_capture_authentication_params_from_url() { if let Err(e) = fs.get( "https://user:pass@github.com/composer/composer/404", IndexMap::new(), - ) && let Some(te) = e.downcast_ref::<TransportException>() + ) && let Some(te) = e.catch::<TransportException>() { assert_ne!(200, te.get_code()); } diff --git a/crates/shirabe/tests/util/silencer_test.rs b/crates/shirabe/tests/util/silencer_test.rs index 000e7371..8c319400 100644 --- a/crates/shirabe/tests/util/silencer_test.rs +++ b/crates/shirabe/tests/util/silencer_test.rs @@ -35,11 +35,7 @@ fn test_silencer() { fn test_silenced_exception() { let verification = format!("{}", microtime()); let err = Silencer::call(|| -> anyhow::Result<()> { - Err(RuntimeException { - message: verification.clone(), - code: 0, - } - .into()) + Err(RuntimeException::new(verification.clone()).into()) }) .unwrap_err(); assert_eq!(verification, err.to_string()); diff --git a/scripts/linters/lint b/scripts/linters/lint index 44ea7f6d..5f59945d 100755 --- a/scripts/linters/lint +++ b/scripts/linters/lint @@ -9,6 +9,7 @@ use Shirabe\Lint\Linters\CargoWorkspaceDependencies; use Shirabe\Lint\Linters\ContiguousUseBlock; use Shirabe\Lint\Linters\NoBannedUse; use Shirabe\Lint\Linters\NoDecorativeSectionComment; +use Shirabe\Lint\Linters\NoExceptionDowncast; use Shirabe\Lint\Linters\NoFormatTrailingComma; use Shirabe\Lint\Linters\NoModRs; use Shirabe\Lint\Linters\NoStdCollectionsMaps; @@ -26,6 +27,14 @@ $runner = new Runner($rootDir, [ [new NoDecorativeSectionComment(), [ 'crates/shirabe-semver/src/version_parser.rs', ]], + [new NoExceptionDowncast(), [ + // Defines the box and the walk the rule points at. + 'crates/shirabe-php-shim/src/exception.rs', + // Downcast a panic payload, not an error: PHP's error handler throws an \ErrorException + // where these call sites `catch` it. + 'crates/shirabe/src/cache.rs', + 'crates/shirabe/src/util/filesystem.rs', + ]], [new NoFormatTrailingComma(), [ 'crates/shirabe/src/package/loader/root_package_loader.rs', 'crates/shirabe-spdx-licenses/src/spdx_licenses.rs', diff --git a/scripts/linters/src/Linters/NoExceptionDowncast.php b/scripts/linters/src/Linters/NoExceptionDowncast.php new file mode 100644 index 00000000..8d60712b --- /dev/null +++ b/scripts/linters/src/Linters/NoExceptionDowncast.php @@ -0,0 +1,76 @@ +<?php + +declare(strict_types=1); + +namespace Shirabe\Lint\Linters; + +use Shirabe\Lint\Linter; +use Shirabe\Lint\Support\FileFinder; +use Shirabe\Lint\Support\Paths; + +/** + * A ported exception travels as an `AnyThrowable`, never as itself, so `downcast_ref::<X>()` + * against an exception type compiles and always answers `None`. It would also answer only for the + * exact class, where PHP's `catch` answers for every subclass too. + */ +final class NoExceptionDowncast implements Linter +{ + private const DOWNCAST_RE = '/\bdowncast(?:_ref|_mut)?::<\s*(?:[A-Za-z_][A-Za-z0-9_]*\s*::\s*)*([A-Za-z_][A-Za-z0-9_]*)\s*>/'; + + private const EXCEPTION_DEFINITION_RE = '/\b(?:impl_php_exception|define_php_exception)!\(\s*(?:@accessors\s+)?\$?([A-Za-z_][A-Za-z0-9_]*)/'; + + public function name(): string + { + return 'no_exception_downcast'; + } + + public function failureIntro(): string + { + return "Found `downcast` against a ported PHP exception type.\n" + . 'An exception is carried by an `AnyThrowable`, so this never matches; ' + . 'use `Catch::catch` / `Catch::catch_mut`, which match subclasses too:'; + } + + public function check(string $rootDir, array $excludes): array + { + $exceptionTypes = $this->exceptionTypes($rootDir); + $errors = []; + + foreach (FileFinder::rustFiles($rootDir) as $path) { + $relative = Paths::relativeTo($rootDir, $path); + if (in_array($relative, $excludes, true)) { + continue; + } + + foreach (file($path) as $idx => $line) { + if (!preg_match(self::DOWNCAST_RE, $line, $m)) { + continue; + } + if (!isset($exceptionTypes[$m[1]])) { + continue; + } + + $errors[] = "{$relative}:" . ($idx + 1) . ": downcast to `{$m[1]}`"; + } + } + + return $errors; + } + + /** @return array<string, true> the type name of every ported exception */ + private function exceptionTypes(string $rootDir): array + { + $types = []; + + foreach (FileFinder::rustFiles($rootDir) as $path) { + preg_match_all(self::EXCEPTION_DEFINITION_RE, file_get_contents($path), $matches); + foreach ($matches[1] as $name) { + if ($name !== 'ty') { + $types[$name] = true; + } + } + } + + return $types; + } +} |
