aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-rpc/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe-php-rpc/src')
-rw-r--r--crates/shirabe-php-rpc/src/frame.rs68
-rw-r--r--crates/shirabe-php-rpc/src/lib.rs17
2 files changed, 85 insertions, 0 deletions
diff --git a/crates/shirabe-php-rpc/src/frame.rs b/crates/shirabe-php-rpc/src/frame.rs
index 3a355c86..fe7752bd 100644
--- a/crates/shirabe-php-rpc/src/frame.rs
+++ b/crates/shirabe-php-rpc/src/frame.rs
@@ -66,6 +66,9 @@ pub enum Frame {
exception_class: String,
message: String,
code: i64,
+ /// The state the exception carries beyond `message` and `code`, keyed by the property
+ /// names its class declares. Empty for an exception that carries none.
+ properties: IndexMap<String, PluginValue>,
},
ReleaseRustHandle {
rhandle: u64,
@@ -180,11 +183,18 @@ impl Frame {
exception_class,
message,
code,
+ properties,
..
} => vec![
PluginValue::string(exception_class.clone()),
PluginValue::string(message.clone()),
PluginValue::Int(*code),
+ PluginValue::Array(
+ properties
+ .iter()
+ .map(|(name, v)| (name.clone().into_bytes(), v.clone()))
+ .collect(),
+ ),
],
Frame::ReleaseRustHandle { rhandle } => vec![int_value(*rhandle)],
Frame::ReleasePhpHandle { phandle } => vec![int_value(*phandle)],
@@ -302,6 +312,7 @@ fn decode_frame(tag: u8, corr_id: u64, payload: &[u8]) -> Frame {
panic!("PHP RPC: protocol violation — Throw code is not an int: {other:?}")
}
},
+ properties: expect_properties(next()),
},
TAG_RELEASE_RUST_HANDLE => Frame::ReleaseRustHandle {
rhandle: expect_id(next()),
@@ -354,6 +365,27 @@ fn expect_positions(value: PluginValue) -> Vec<u32> {
.collect()
}
+fn expect_properties(value: PluginValue) -> IndexMap<String, PluginValue> {
+ match value {
+ PluginValue::List(items) if items.is_empty() => IndexMap::new(),
+ PluginValue::Array(map) => map
+ .into_iter()
+ .map(|(key, item)| {
+ let name = String::from_utf8(key).unwrap_or_else(|error| {
+ panic!(
+ "PHP RPC: protocol violation — exception property name is not UTF-8: {:?}",
+ String::from_utf8_lossy(error.as_bytes())
+ )
+ });
+ (name, item)
+ })
+ .collect(),
+ other => {
+ panic!("PHP RPC: protocol violation — exception properties is not an array: {other:?}")
+ }
+ }
+}
+
fn expect_out_params(value: PluginValue) -> IndexMap<u32, PluginValue> {
match value {
PluginValue::List(items) if items.is_empty() => IndexMap::new(),
@@ -416,6 +448,42 @@ mod tests {
}
#[test]
+ fn frame_roundtrip_throw_with_properties() {
+ let frame = roundtrip(Frame::Throw {
+ corr_id: 11,
+ exception_class: "Composer\\Downloader\\TransportException".to_string(),
+ message: "The \"https://example.org\" file could not be downloaded".to_string(),
+ code: 401,
+ properties: [
+ ("statusCode".to_string(), PluginValue::Int(401)),
+ ("response".to_string(), PluginValue::Null),
+ ]
+ .into_iter()
+ .collect(),
+ });
+ match frame {
+ Frame::Throw {
+ corr_id,
+ exception_class,
+ message,
+ code,
+ properties,
+ } => {
+ assert_eq!(corr_id, 11);
+ assert_eq!(exception_class, "Composer\\Downloader\\TransportException");
+ assert_eq!(
+ message,
+ "The \"https://example.org\" file could not be downloaded"
+ );
+ assert_eq!(code, 401);
+ assert_eq!(properties.get("statusCode"), Some(&PluginValue::Int(401)));
+ assert_eq!(properties.get("response"), Some(&PluginValue::Null));
+ }
+ other => panic!("unexpected frame: {other:?}"),
+ }
+ }
+
+ #[test]
fn frame_roundtrip_return_with_out_params() {
let frame = roundtrip(Frame::Return {
corr_id: 9,
diff --git a/crates/shirabe-php-rpc/src/lib.rs b/crates/shirabe-php-rpc/src/lib.rs
index dfd8fa67..c898fb82 100644
--- a/crates/shirabe-php-rpc/src/lib.rs
+++ b/crates/shirabe-php-rpc/src/lib.rs
@@ -621,6 +621,15 @@ pub struct PhpThrow {
pub exception_class: String,
pub message: String,
pub code: i64,
+ /// The state the exception carries beyond `message` and `code`, keyed by the property names
+ /// its class declares. The child revives them onto the instance it rebuilds.
+ ///
+ /// TODO(plugin): populated only Rust to PHP. An exception a plugin throws crosses with its
+ /// class, message and code alone, because no Rust-side consumer rebuilds a ported exception
+ /// from a `PhpThrow` yet.
+ ///
+ /// Boxed so a `Result<_, PhpThrow>` stays small; every dispatcher returns one.
+ pub properties: Box<IndexMap<String, PluginValue>>,
}
impl PhpThrow {
@@ -629,6 +638,7 @@ impl PhpThrow {
exception_class: "RuntimeException".to_string(),
message,
code: 0,
+ properties: Box::new(IndexMap::new()),
}
}
}
@@ -805,11 +815,13 @@ fn rpc_call(
exception_class,
message,
code,
+ properties,
} if corr_id == my_id => {
return Ok(Err(PhpThrow {
exception_class,
message,
code,
+ properties: Box::new(properties),
}));
}
Frame::CallRustMethod {
@@ -846,6 +858,7 @@ fn rpc_call(
exception_class: throw.exception_class,
message: throw.message,
code: throw.code,
+ properties: *throw.properties,
},
};
send_frame(&reply)?;
@@ -1042,6 +1055,10 @@ const RUNTIME_FILES: &[(&str, &str)] = &[
include_str!("../php/runtime/Composer/EventDispatcher/Event.php"),
),
(
+ "Shirabe/MaterializedThrowable.php",
+ include_str!("../php/runtime/Shirabe/MaterializedThrowable.php"),
+ ),
+ (
"Shirabe/MaterializedValue.php",
include_str!("../php/runtime/Shirabe/MaterializedValue.php"),
),