#![no_std]
use core::fmt::Debug;
use xdrs::{ReadError, ReadXDR, ReadXDROwned, WriteXDR, cxs};
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a contract exception",
label = "not a contract exception",
note = "annotate `{Self}` with `#[contract_exception]` (or `#[exn]` if \
you handle your own derives); `throws = ...` in \
`#[contract(...)]` requires it"
)]
pub trait Exn: Debug + PartialEq + WriteXDR {
const SELECTOR: u64;
}
impl<T: Exn> Exn for &T {
const SELECTOR: u64 = T::SELECTOR;
}
impl Exn for core::convert::Infallible {
const SELECTOR: u64 = 0;
}
#[derive(Clone, Copy)]
pub struct RawExceptionRef<'s> {
selector: u64,
wire: &'s [u8],
payload_start: usize,
}
impl<'s> RawExceptionRef<'s> {
pub fn parse(wire: &'s [u8]) -> Result<Self, ReadError> {
let mut reader = cxs::buf_reader::BufReader::new(wire);
let selector: u64 = ReadXDR::read_xdr(&mut reader)?;
Ok(Self {
selector,
wire,
payload_start: wire.len() - reader.remaining_slice().len(),
})
}
#[doc(hidden)]
pub fn from_parts(
selector: u64,
wire: &'s [u8],
payload_start: usize,
) -> Self {
debug_assert!(
payload_start <= wire.len(),
"payload_start {payload_start} past wire end {}",
wire.len()
);
Self {
selector,
wire,
payload_start,
}
}
pub fn selector(&self) -> u64 {
self.selector
}
pub fn wire(&self) -> &'s [u8] {
self.wire
}
pub fn payload(&self) -> &'s [u8] {
&self.wire[self.payload_start..]
}
pub fn payload_start(&self) -> usize {
self.payload_start
}
pub fn to_owned<O: From<Self>>(self) -> O {
O::from(self)
}
pub fn try_as<T: Exn + ReadXDROwned>(&self) -> Option<T> {
self.try_decode().and_then(|decoded| decoded.ok())
}
pub fn try_decode<T: Exn + ReadXDROwned>(
&self,
) -> Option<Result<T, ReadError>> {
if self.selector != T::SELECTOR {
return None;
}
let mut reader = cxs::buf_reader::BufReader::new(self.payload());
Some(ReadXDR::read_xdr(&mut reader))
}
}
impl core::fmt::Debug for RawExceptionRef<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let payload = self.payload();
write!(
f,
"RawExceptionRef {{ selector: 0x{:016X}, payload: {} bytes",
self.selector,
payload.len(),
)?;
#[cfg(not(target_arch = "riscv32"))]
{
write!(f, ", hex: ")?;
for b in payload {
write!(f, "{b:02x}")?;
}
}
write!(f, " }}")
}
}
#[cfg(test)]
extern crate std;
#[cfg(test)]
mod tests {
use super::*;
use std::vec::Vec;
#[derive(Debug, PartialEq, xdrs::WriteXDR, xdrs::ReadXDR)]
struct TestExn(u32);
impl Exn for TestExn {
const SELECTOR: u64 = 0xABCD;
}
fn wire(selector: u64, payload: &[u8]) -> Vec<u8> {
let mut wire = cxs::encode(&selector).unwrap();
wire.extend_from_slice(payload);
wire
}
#[test]
fn parse_separates_selector_and_payload() {
let wire = wire(0xABCD, b"payload-bytes");
let exn = RawExceptionRef::parse(&wire).unwrap();
assert_eq!(exn.selector(), 0xABCD);
assert_eq!(exn.payload(), b"payload-bytes");
assert_eq!(exn.wire(), &wire[..]);
assert_eq!(exn.payload_start(), wire.len() - b"payload-bytes".len());
}
#[test]
fn parse_rejects_garbage() {
assert!(RawExceptionRef::parse(&[]).is_err());
}
#[test]
fn try_decode_distinguishes_selector_from_payload_failures() {
let payload = cxs::encode(&7u32).unwrap();
let ok = wire(TestExn::SELECTOR, &payload);
assert!(matches!(
RawExceptionRef::parse(&ok).unwrap().try_decode::<TestExn>(),
Some(Ok(TestExn(7)))
));
assert_eq!(
RawExceptionRef::parse(&ok).unwrap().try_as::<TestExn>(),
Some(TestExn(7))
);
let other = wire(TestExn::SELECTOR + 1, &payload);
assert!(
RawExceptionRef::parse(&other)
.unwrap()
.try_decode::<TestExn>()
.is_none()
);
let truncated = wire(TestExn::SELECTOR, &[]);
assert!(matches!(
RawExceptionRef::parse(&truncated)
.unwrap()
.try_decode::<TestExn>(),
Some(Err(_))
));
assert_eq!(
RawExceptionRef::parse(&truncated)
.unwrap()
.try_as::<TestExn>(),
None
);
}
}