use xabi::XabiType;
#[xabi::data]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DataPoint {
pub id: u32,
pub count: u64,
}
#[xabi::data]
#[derive(Debug, PartialEq, Eq)]
pub struct DataEnvelope {
pub point: DataPoint,
pub label: String,
pub payload: Vec<u8>,
pub enabled: bool,
pub slots: usize,
}
#[xabi::data]
#[derive(Debug, PartialEq, Eq)]
pub struct DataError {
pub message: String,
}
#[xabi::data]
#[derive(Debug, PartialEq, Eq)]
pub struct OptionalData {
pub label: Option<String>,
pub payload: Option<Vec<u8>>,
}
#[xabi::data]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SizedRange {
pub offset: Option<u64>,
pub size: Option<u64>,
}
#[xabi::data]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WideNumbers {
pub unsigned: u128,
pub signed: i128,
}
#[xabi::opaque]
#[derive(Clone, Copy, Debug)]
pub struct OpaqueCounter {
raw: *mut u32,
}
unsafe impl Send for OpaqueCounter {}
#[test]
fn data_macro_generates_wire_type_and_roundtrips() -> xabi::Result<()> {
let point = DataPoint::new(7_u32, 42_u64);
let wire = point.into_wire();
assert_eq!(wire.size, std::mem::size_of::<XabiV1DataDataPoint>());
assert_eq!(wire.abi_version, xabi::ABI_VERSION);
wire.validate()?;
let decoded = unsafe { DataPoint::from_wire(&wire) }?;
assert_eq!(decoded, point);
let payload = point.into_payload();
let decoded = unsafe { DataPoint::from_payload(payload) }?;
assert_eq!(decoded, point);
Ok(())
}
#[test]
fn data_macro_lowers_nested_xabi_fields_and_owned_payloads() -> xabi::Result<()> {
let envelope = DataEnvelope::new(
DataPoint::new(9_u32, 11_u64),
"nested",
vec![1, 2, 3],
true,
4_usize,
);
let wire = envelope.into_wire();
assert_eq!(wire.size, std::mem::size_of::<XabiV1DataDataEnvelope>());
assert_eq!(wire.abi_version, xabi::ABI_VERSION);
assert_eq!(wire.point.id, 9);
assert_eq!(wire.enabled, 1);
wire.validate()?;
let decoded = unsafe { DataEnvelope::from_wire(&wire) }?;
assert_eq!(
decoded,
DataEnvelope {
point: DataPoint { id: 9, count: 11 },
label: "nested".to_string(),
payload: vec![1, 2, 3],
enabled: true,
slots: 4,
}
);
let payload = DataEnvelope::new(
DataPoint::new(1_u32, 2_u64),
"payload",
vec![8, 13],
false,
21_usize,
)
.into_payload();
let decoded = unsafe { DataEnvelope::from_payload(payload) }?;
assert_eq!(decoded.label, "payload");
assert_eq!(decoded.payload, vec![8, 13]);
assert!(!decoded.enabled);
Ok(())
}
#[test]
fn data_macro_can_back_error_payloads() -> xabi::Result<()> {
let err = DataError::new("failed");
let payload = err.into_payload();
let decoded = unsafe { DataError::from_payload(payload) }?;
assert_eq!(decoded.message, "failed");
Ok(())
}
#[test]
fn option_xabi_type_preserves_empty_some_values() -> xabi::Result<()> {
let value = OptionalData::new(Some(String::new()), Some(Vec::new()));
let decoded = unsafe { OptionalData::from_payload(value.into_payload()) }?;
assert_eq!(decoded.label, Some(String::new()));
assert_eq!(decoded.payload, Some(Vec::new()));
let value = OptionalData::new(None, None);
let decoded = unsafe { OptionalData::from_payload(value.into_payload()) }?;
assert_eq!(decoded.label, None);
assert_eq!(decoded.payload, None);
Ok(())
}
#[test]
fn data_macro_allows_user_field_named_size() -> xabi::Result<()> {
let value = SizedRange::new(Some(4), Some(8));
let wire = value.clone().into_wire();
assert_eq!(wire.size, std::mem::size_of::<XabiV1DataSizedRange>());
wire.validate()?;
let decoded = unsafe { SizedRange::from_wire(&wire) }?;
assert_eq!(decoded, value);
Ok(())
}
#[test]
fn data_layout_uses_wire_offsets_for_reserved_field_names() {
let mut items = Vec::new();
<SizedRange as XabiType>::collect_xabi_layout(&mut items);
let layout = items
.iter()
.find_map(|item| {
let xabi::XabiLayoutItem::Type(layout) = item else {
return None;
};
if layout.name.ends_with("::XabiV1DataSizedRange") {
Some(layout)
} else {
None
}
})
.expect("SizedRange layout is collected");
let field = layout
.fields
.iter()
.find(|field| field.name == "size" && field.ty == "XabiOption")
.expect("user size field is collected");
assert_eq!(
field.offset,
std::mem::offset_of!(XabiV1DataSizedRange, __xabi_field_size)
);
}
#[test]
fn native_128_bit_integers_work_in_data_and_payloads() -> xabi::Result<()> {
let unsigned = 0x0123_4567_89ab_cdef_fedc_ba98_7654_3210_u128;
let signed = -0x0123_4567_89ab_cdef_0123_4567_89ab_cdef_i128;
let wide = WideNumbers::new(unsigned, signed);
let wire = wide.into_wire();
let _: <u128 as XabiType>::Wire = unsigned;
let _: <i128 as XabiType>::Wire = signed;
assert_eq!(wire.unsigned, unsigned);
assert_eq!(wire.signed, signed);
assert_eq!(unsafe { WideNumbers::from_wire(&wire) }?, wide);
assert_eq!(
unsafe { WideNumbers::from_payload(wide.into_payload()) }?,
wide
);
let optional_unsigned = Some(u128::MAX).into_wire();
assert_eq!(
unsafe { <Option<u128> as XabiType>::from_wire(&optional_unsigned) }?,
Some(u128::MAX)
);
let optional_signed = Some(i128::MIN).into_wire();
assert_eq!(
unsafe { <Option<i128> as XabiType>::from_wire(&optional_signed) }?,
Some(i128::MIN)
);
let mut items = Vec::new();
<WideNumbers as XabiType>::collect_xabi_layout(&mut items);
let layout = items
.iter()
.find_map(|item| {
let xabi::XabiLayoutItem::Type(layout) = item else {
return None;
};
if layout.name.ends_with("::XabiV1DataWideNumbers") {
Some(layout)
} else {
None
}
})
.expect("WideNumbers layout is collected");
assert_eq!(
layout
.fields
.iter()
.find(|field| field.name == "unsigned")
.expect("unsigned field is collected")
.ty,
"u128"
);
assert_eq!(
layout
.fields
.iter()
.find(|field| field.name == "signed")
.expect("signed field is collected")
.ty,
"i128"
);
Ok(())
}
#[test]
fn opaque_macro_generates_non_null_pointer_handle() -> xabi::Result<()> {
let mut counter = 7_u32;
let handle = unsafe { OpaqueCounter::from_raw(&mut counter) }?;
let wire = handle.into_wire();
assert_eq!(wire.size, std::mem::size_of::<XabiV1OpaqueOpaqueCounter>());
assert_eq!(wire.abi_version, xabi::ABI_VERSION);
assert_eq!(wire.raw, &mut counter as *mut u32);
wire.validate()?;
let decoded = unsafe { OpaqueCounter::from_wire(&wire) }?;
unsafe {
*decoded.as_raw() = 11;
}
assert_eq!(counter, 11);
Ok(())
}
#[test]
fn opaque_macro_rejects_null_pointer() {
let err = unsafe { OpaqueCounter::from_raw(std::ptr::null_mut()) }
.expect_err("null opaque handle must fail");
assert!(err.to_string().contains("OpaqueCounter::raw"));
}
#[test]
fn data_macro_rejects_invalid_wire_size() {
let mut wire = DataPoint::new(7_u32, 42_u64).into_wire();
wire.size = 0;
let err = unsafe { DataPoint::from_wire(&wire) }.expect_err("invalid size must fail");
assert!(err.to_string().contains("does not match expected"));
}