aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-shim/src/phar.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-08 22:14:12 +0900
committernsfisis <nsfisis@gmail.com>2026-08-08 22:14:12 +0900
commitf4cad2123b2af0de72bda4ce039e16e74f163f4e (patch)
tree21803308c5ff41e23c9d3b117433eea16b4ff663 /crates/shirabe-php-shim/src/phar.rs
parent0209f63210e5b547b5c6b73367bb80ea86c255ec (diff)
downloadphp-shirabe-f4cad2123b2af0de72bda4ce039e16e74f163f4e.tar.gz
php-shirabe-f4cad2123b2af0de72bda4ce039e16e74f163f4e.tar.zst
php-shirabe-f4cad2123b2af0de72bda4ce039e16e74f163f4e.zip
feat(php-shim): give ported exceptions PHP's class hierarchy
Ported exceptions were flat structs reached with `downcast_ref`, so Composer's `catch (\RuntimeException $e)` only matched the exact leaf type and `get_class($e)` had nothing to report. Each exception now embeds an instance of the class it extends and travels inside an `AnyThrowable`; `Catch::catch`/`catch_mut` walk that chain, and `PhpClass::php_class_name` yields the PHP FQCN. Dropping the `std::error::Error` impls from the exception types leaves `AnyThrowable` as the only route into an `anyhow::Error`, so the walk cannot be bypassed. A `no_exception_downcast` linter catches the `downcast::<X>()` calls that would now silently answer `None`. Three sites change behavior as a result: the `TransportException` exit-code override reaches `MaxFileSizeExceededException`, the `catch (\LogicException)` in findSimilar() reaches its subclasses, and rendered exception titles carry the real class name rather than a guess. `get_class_err()` is no longer a `todo!()`, which re-enables FilesystemRepositoryTest::testCorruptedRepositoryFile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-shim/src/phar.rs')
-rw-r--r--crates/shirabe-php-shim/src/phar.rs137
1 files changed, 61 insertions, 76 deletions
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")
);
}