use core::any::Any;
use polyplug_abi::{SupportedLanguage, types::AbiError};
#[derive(Debug, thiserror::Error)]
pub enum BridgeError {
#[error("duplicate host contract registration: contract_id=0x{contract_id:016X}")]
DuplicateContract { contract_id: u64 },
#[error("host contract not found: contract_id=0x{contract_id:016X}")]
ContractNotFound { contract_id: u64 },
#[error(
"implementation type mismatch for contract_id=0x{contract_id:016X}: expected {expected}, got {got}"
)]
TypeMismatch {
contract_id: u64,
expected: String,
got: String,
},
#[error("VM registration failed for contract_id=0x{contract_id:016X}: {reason}")]
VmRegistrationFailed { contract_id: u64, reason: String },
#[error("VM dispatch failed for contract_id=0x{contract_id:016X} fn_id={fn_id}: {reason}")]
VmDispatchFailed {
contract_id: u64,
fn_id: u32,
reason: String,
},
#[error("host runtime bridge not initialized for runtime={runtime:?}")]
BridgeNotInitialized { runtime: SupportedLanguage },
}
pub trait RuntimeLanguageBridge: Send + Sync {
fn runtime_type(&self) -> SupportedLanguage;
fn register_host_contract(
&mut self,
contract_id: u64,
implementation: Box<dyn Any>,
) -> Result<(), BridgeError>;
unsafe fn call_host_contract(
&self,
contract_id: u64,
fn_id: u32,
args: *const (),
out: *mut (),
) -> AbiError;
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used)]
use super::*;
#[test]
fn bridge_error_duplicate_contract_display() {
let err: BridgeError = BridgeError::DuplicateContract {
contract_id: 0xABCD_EF01_2345_6789,
};
let s: String = err.to_string();
assert!(s.contains("duplicate"), "got: {s}");
assert!(s.to_lowercase().contains("abcdef01"), "got: {s}");
}
#[test]
fn bridge_error_contract_not_found_display() {
let err: BridgeError = BridgeError::ContractNotFound {
contract_id: 0x1234_5678_9ABC_DEF0,
};
let s: String = err.to_string();
assert!(s.contains("not found"), "got: {s}");
assert!(s.to_lowercase().contains("12345678"), "got: {s}");
}
#[test]
fn bridge_error_type_mismatch_display() {
let err: BridgeError = BridgeError::TypeMismatch {
contract_id: 0xCAFE_BABE_0000_0001,
expected: "PyObject".to_owned(),
got: "LuaTable".to_owned(),
};
let s: String = err.to_string();
assert!(s.contains("type mismatch"), "got: {s}");
assert!(s.contains("PyObject"), "got: {s}");
assert!(s.contains("LuaTable"), "got: {s}");
}
#[test]
fn bridge_error_vm_registration_failed_display() {
let err: BridgeError = BridgeError::VmRegistrationFailed {
contract_id: 0xDEAD_BEEF_0000_0001,
reason: "interpreter shutdown".to_owned(),
};
let s: String = err.to_string();
assert!(s.contains("registration failed"), "got: {s}");
assert!(s.contains("interpreter shutdown"), "got: {s}");
}
#[test]
fn bridge_error_vm_dispatch_failed_display() {
let err: BridgeError = BridgeError::VmDispatchFailed {
contract_id: 0xF00D_0000_0001,
fn_id: 3,
reason: "Python exception raised".to_owned(),
};
let s: String = err.to_string();
assert!(s.contains("dispatch failed"), "got: {s}");
assert!(s.contains('3'), "got: {s}");
assert!(s.contains("Python exception"), "got: {s}");
}
#[test]
fn bridge_error_bridge_not_initialized_display() {
let err: BridgeError = BridgeError::BridgeNotInitialized {
runtime: SupportedLanguage::Python,
};
let s: String = err.to_string();
assert!(s.contains("not initialized"), "got: {s}");
assert!(s.contains("Python"), "got: {s}");
}
}