irys 0.2.0

Compile-time trait reflection for Rust
Documentation
use irys::*;
use std::fmt;

trait DynSerialize {
    fn to_string_repr(&self) -> String;
}

impl<T: fmt::Debug> DynSerialize for T {
    fn to_string_repr(&self) -> String {
        format!("{:?}", self)
    }
}

trait Resettable {
    fn reset(&mut self);
    fn value(&self) -> String;
}

struct DisplayCap;
impl Capability for DisplayCap {
    type Handle = dyn fmt::Display;
}

struct DebugCap;
impl Capability for DebugCap {
    type Handle = dyn fmt::Debug;
}

struct SerializeCap;
impl Capability for SerializeCap {
    type Handle = dyn DynSerialize;
}

struct ResettableCap;
impl Capability for ResettableCap {
    type Handle = dyn Resettable;
}

register_capability! {
    slot = 0,
    cap = DisplayCap,
    trait_bound = fmt::Display,
}

register_capability! {
    slot = 1,
    cap = DebugCap,
    trait_bound = fmt::Debug,
}

register_capability! {
    slot = 2,
    cap = SerializeCap,
    trait_bound = DynSerialize,
}

register_capability! {
    slot = 3,
    cap = ResettableCap,
    trait_bound = Resettable,
}

#[derive(Debug, Clone)]
struct FullType {
    name: String,
    value: i32,
}

impl fmt::Display for FullType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}={}", self.name, self.value)
    }
}

#[derive(Debug)]
struct DebugOnly {
    x: u32,
}

struct OpaqueType {
    _data: Vec<u8>,
}

struct Counter {
    count: u32,
}

impl Resettable for Counter {
    fn reset(&mut self) { self.count = 0; }
    fn value(&self) -> String { format!("count={}", self.count) }
}

#[test]
fn test_full_type_capabilities() {
    let envelope = reflect!(FullType { name: "test".into(), value: 42 });

    assert!(envelope.has::<DisplayCap>());
    assert!(envelope.has::<DebugCap>());
    assert!(envelope.has::<SerializeCap>());
    assert!(!envelope.has::<ResettableCap>());
    assert_eq!(envelope.capability_count(), 3);
}

#[test]
fn test_debug_only_capabilities() {
    let envelope = reflect!(DebugOnly { x: 99 });

    assert!(!envelope.has::<DisplayCap>());
    assert!(envelope.has::<DebugCap>());
    assert!(envelope.has::<SerializeCap>());
    assert!(!envelope.has::<ResettableCap>());
}

#[test]
fn test_opaque_no_capabilities() {
    let envelope = reflect!(OpaqueType { _data: vec![1, 2, 3] });

    assert!(!envelope.has::<DisplayCap>());
    assert!(!envelope.has::<DebugCap>());
    assert!(!envelope.has::<SerializeCap>());
    assert!(!envelope.has::<ResettableCap>());
    assert_eq!(envelope.capability_count(), 0);
}

#[test]
fn test_get_returns_working_trait_object() {
    let envelope = reflect!(FullType { name: "hello".into(), value: 7 });

    let display = envelope.get::<DisplayCap>().unwrap();
    assert_eq!(format!("{}", display), "hello=7");

    let debug = envelope.get::<DebugCap>().unwrap();
    assert_eq!(format!("{:?}", debug), "FullType { name: \"hello\", value: 7 }");
}

#[test]
fn test_get_mut_allows_mutation() {
    let mut envelope = reflect!(Counter { count: 42 });

    assert!(envelope.has::<ResettableCap>());

    let r = envelope.get::<ResettableCap>().unwrap();
    assert_eq!(r.value(), "count=42");

    envelope.get_mut::<ResettableCap>().unwrap().reset();

    let r = envelope.get::<ResettableCap>().unwrap();
    assert_eq!(r.value(), "count=0");
}

#[test]
fn test_get_on_missing_capability_returns_none() {
    let envelope = reflect!(OpaqueType { _data: vec![] });

    assert!(envelope.get::<DisplayCap>().is_none());
    assert!(envelope.get::<DebugCap>().is_none());
}

#[test]
fn test_data_access() {
    let envelope = reflect!(FullType { name: "original".into(), value: 100 });

    let data = envelope.data::<FullType>().unwrap();
    assert_eq!(data.name, "original");
    assert_eq!(data.value, 100);
}

#[test]
fn test_into_data() {
    let envelope = reflect!(FullType { name: "owned".into(), value: 55 });

    let data = envelope.into_data::<FullType>().unwrap();
    assert_eq!(data.name, "owned");
    assert_eq!(data.value, 55);
}

#[test]
fn test_data_wrong_type_returns_none() {
    let envelope = reflect!(FullType { name: "x".into(), value: 1 });

    assert!(envelope.data::<Counter>().is_none());
}

#[test]
fn test_custom_registry() {
    struct MyRegistry;

    struct MyDebugCap;
    impl Capability for MyDebugCap {
        type Handle = dyn fmt::Debug;
    }

    register_capability! {
        registry = MyRegistry,
        slot = 0,
        cap = MyDebugCap,
        trait_bound = fmt::Debug,
    }

    let envelope = reflect!(FullType { name: "hi".into(), value: 1 }, [{ registry: MyRegistry, slots: 0..5 }]);

    assert!(envelope.has::<MyDebugCap>());
    assert!(!envelope.has::<DisplayCap>()); // not probed
    assert_eq!(envelope.capability_count(), 1);
}

#[test]
fn test_multiple_registries() {
    struct RegA;
    struct RegB;

    struct CapA;
    impl Capability for CapA {
        type Handle = dyn fmt::Display;
    }

    struct CapB;
    impl Capability for CapB {
        type Handle = dyn fmt::Debug;
    }

    register_capability! {
        registry = RegA,
        slot = 0,
        cap = CapA,
        trait_bound = fmt::Display,
    }

    register_capability! {
        registry = RegB,
        slot = 0,
        cap = CapB,
        trait_bound = fmt::Debug,
    }

    let envelope = reflect!(FullType { name: "multi".into(), value: 9 }, [
        { registry: RegA, slots: 0..5 },
        { registry: RegB, slots: 0..5 },
    ]);

    assert!(envelope.has::<CapA>());
    assert!(envelope.has::<CapB>());
    assert_eq!(envelope.capability_count(), 2);
}

#[test]
fn test_custom_range() {
    let envelope = reflect!(FullType { name: "range".into(), value: 0 }, [
        { registry: DefaultRegistry, slots: 0..2 },
    ]);

    assert!(envelope.has::<DisplayCap>()); // slot 0
    assert!(envelope.has::<DebugCap>());   // slot 1
    assert!(!envelope.has::<SerializeCap>()); // slot 2, not probed
    assert!(!envelope.has::<ResettableCap>()); // slot 3, not probed
}