diff options
Diffstat (limited to 'crates/shirabe-php-rpc/src')
| -rw-r--r-- | crates/shirabe-php-rpc/src/frame.rs | 68 | ||||
| -rw-r--r-- | crates/shirabe-php-rpc/src/lib.rs | 17 |
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"), ), |
