xabi 0.1.0-alpha.3

Generate stable native ABI glue from Rust traits
Documentation
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::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 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_prefix() {
    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("smaller than expected"));
}