use crate::exn::Exn;
#[cfg(feature = "alloc")]
use crate::exn::{CallingError, FaultString, LibraryExn};
use crate::xdr::*;
use thruput_exn::RawExceptionRef;
impl crate::re_throw::ReThrow for RawExceptionRef<'_> {
#[inline]
#[cfg_attr(not(target_arch = "riscv32"), track_caller)]
fn re_throw(self) -> ! {
#[cfg(all(not(target_arch = "riscv32"), feature = "std"))]
crate::contract_op::abort_breadcrumb("Re-throwing", "Location", &self);
crate::contract_op::contract_abort(self.wire())
}
}
impl From<RawExceptionRef<'_>> for RawException {
fn from(exn: RawExceptionRef<'_>) -> Self {
RawException::own_wire(&exn)
}
}
fn fmt_exn(
f: &mut core::fmt::Formatter,
name: &str,
selector: u64,
payload: &[u8],
) -> core::fmt::Result {
write!(
f,
"{name} {{ selector: 0x{selector:016X}, payload: {} bytes",
payload.len(),
)?;
#[cfg(not(target_arch = "riscv32"))]
write!(f, ", hex: {}", crate::hex::hex_encode(&payload))?;
write!(f, " }}")
}
pub struct RawException {
selector: u64,
wire: crate::call::Buf,
payload_start: usize,
}
impl RawException {
fn own_wire(exn: &RawExceptionRef<'_>) -> Self {
let selector = exn.selector();
let payload_start = exn.payload_start();
let wire = exn.wire();
#[cfg(feature = "alloc")]
let wire = wire.to_vec();
#[cfg(not(feature = "alloc"))]
let wire = {
let mut buf = crate::call::Buf::new();
if buf.extend_from_slice(wire).is_err() {
crate::contract_op::contract_abort(wire);
}
buf
};
Self {
selector,
wire,
payload_start,
}
}
#[inline]
pub fn selector(&self) -> u64 {
self.selector
}
#[inline]
pub fn payload(&self) -> &[u8] {
&self.wire.as_slice()[self.payload_start..]
}
pub(crate) fn as_ref_exn(&self) -> RawExceptionRef<'_> {
RawExceptionRef::from_parts(
self.selector,
self.wire.as_slice(),
self.payload_start,
)
}
pub fn try_as<T: Exn + ReadXDROwned>(&self) -> Option<T> {
self.as_ref_exn().try_as()
}
pub fn try_decode<T: Exn + ReadXDROwned>(
&self,
) -> Option<Result<T, ReadError>> {
self.as_ref_exn().try_decode()
}
#[inline]
#[cfg_attr(not(target_arch = "riscv32"), track_caller)]
pub fn re_throw(&self) -> ! {
#[cfg(all(not(target_arch = "riscv32"), feature = "std"))]
crate::contract_op::abort_breadcrumb("Re-throwing", "Location", self);
crate::contract_op::contract_abort(self.wire.as_ref())
}
#[cfg(feature = "alloc")]
pub fn new(selector: u64, payload: &[u8]) -> Self {
let mut wire = crate::internal::encode_xdr(&selector)
.expect("u64 CXS-encode cannot fail under alloc");
let payload_start = wire.len();
wire.extend_from_slice(payload);
Self {
selector,
wire,
payload_start,
}
}
#[cfg(feature = "alloc")]
pub fn from_abort_buf(bytes: &[u8]) -> Result<Self, LibraryExn> {
let exn = RawExceptionRef::parse(bytes).map_err(|e| {
LibraryExn::Calling(CallingError::DecodingException(
FaultString::from_display(e),
))
})?;
Ok(exn.to_owned())
}
}
impl PartialEq for RawException {
fn eq(&self, other: &Self) -> bool {
self.selector == other.selector && self.payload() == other.payload()
}
}
impl core::fmt::Debug for RawException {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
fmt_exn(f, "RawException", self.selector, self.payload())
}
}
impl crate::re_throw::ReThrow for RawException {
#[cfg_attr(not(target_arch = "riscv32"), track_caller)]
fn re_throw(self) -> ! {
RawException::re_throw(&self)
}
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::*;
#[test]
fn parse_extracts_selector_and_payload() {
let mut wire = crate::internal::encode_xdr(&0xABCDu64).unwrap();
wire.extend_from_slice(b"payload-bytes");
let exn = RawExceptionRef::parse(&wire).unwrap();
assert_eq!(exn.selector(), 0xABCD);
assert_eq!(exn.payload(), b"payload-bytes");
}
#[test]
fn parse_rejects_garbage() {
assert!(RawExceptionRef::parse(&[]).is_err());
}
#[test]
fn new_roundtrips_selector_and_payload() {
let c = RawException::new(0x1234, b"hello");
assert_eq!(c.selector(), 0x1234);
assert_eq!(c.payload(), b"hello");
}
#[test]
fn from_abort_buf_parses_wire_format() {
let mut wire = crate::internal::encode_xdr(&0xABCDu64).unwrap();
wire.extend_from_slice(b"payload-bytes");
let c = RawException::from_abort_buf(&wire).unwrap();
assert_eq!(c.selector(), 0xABCD);
assert_eq!(c.payload(), b"payload-bytes");
}
#[test]
fn holds_oversized_payload_without_truncation_under_alloc() {
let big = [0xABu8; crate::internal::SCRATCH_MAX + 1];
let c = RawException::new(0x1234, &big);
assert_eq!(c.payload(), &big[..]);
}
#[test]
fn eq_compares_selector_and_payload_bytes() {
assert_eq!(RawException::new(1, b"x"), RawException::new(1, b"x"));
assert_ne!(RawException::new(1, b"x"), RawException::new(2, b"x"));
assert_ne!(RawException::new(1, b"x"), RawException::new(1, b"y"));
}
#[test]
fn try_as_decodes_matching_selector_and_payload() {
let payload =
crate::internal::encode_xdr(&LibraryExn::NonNegativeIntOverflow)
.unwrap();
let c = RawException::new(LibraryExn::SELECTOR, &payload);
assert_eq!(
c.try_as::<LibraryExn>(),
Some(LibraryExn::NonNegativeIntOverflow)
);
}
#[test]
fn try_as_returns_none_for_mismatched_selector() {
let payload =
crate::internal::encode_xdr(&LibraryExn::NonNegativeIntOverflow)
.unwrap();
let c = RawException::new(!LibraryExn::SELECTOR, &payload);
assert_eq!(c.try_as::<LibraryExn>(), None);
}
#[test]
fn try_as_returns_none_for_undecodable_payload() {
let payload =
crate::internal::encode_xdr(&LibraryExn::NonNegativeIntOverflow)
.unwrap();
let truncated = &payload[..payload.len().saturating_sub(1)];
let c = RawException::new(LibraryExn::SELECTOR, truncated);
assert_eq!(c.try_as::<LibraryExn>(), None);
}
}