minfac 0.1.5

Lightweight Inversion Of Control
Documentation
use abi_stable::{
    type_layout::{ModPath, TypeLayout},
    StableAbi,
};
use std::{
    ffi::CStr,
    hash::{Hash, Hasher},
};

use crate::strategy::Identifyable;

#[repr(C)]
#[derive(Debug, StableAbi)]
pub struct StableAbiStrategy {}

impl crate::Strategy for StableAbiStrategy {
    type Id = StableAbiTypeId;
}

#[repr(C)]
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Copy, Hash)]
pub struct StableAbiTypeId {
    name: &'static str,
    version: &'static str,
    path: &'static CStr,
    child_name_hash: u64,
}

impl<T: StableAbi + 'static> Identifyable<StableAbiTypeId> for T {
    fn get_id() -> StableAbiTypeId {
        get_layout_typeid(Self::LAYOUT)
    }
}

fn get_layout_typeid(layout: &'static TypeLayout) -> StableAbiTypeId {
    let path: ModPath = layout.mod_path();
    // Hack to access path: Both ModPath & it's inner NulStr have repr(transparent)
    // NulStr represents a immutable, static nullterminated string
    let path_str = unsafe {
        let path_trans: &*const i8 = std::mem::transmute(&path);
        CStr::from_ptr(*path_trans)
    };

    // TraitObjects in RBox and RArc have the same type_id otherwise
    let mut hasher = Djb2::default();
    hash_child_layout_names(&mut hasher, layout);

    StableAbiTypeId {
        name: layout.name(),
        version: layout.item_info().package_and_version().1,
        path: path_str,
        child_name_hash: hasher.finish(),
    }
}

// String hasher which will not change between between releases as the rust-Hasher might
struct Djb2(u64);
impl Default for Djb2 {
    fn default() -> Self {
        Self(5381)
    }
}
impl Hasher for Djb2 {
    fn finish(&self) -> u64 {
        self.0
    }

    fn write(&mut self, bytes: &[u8]) {
        for byte in bytes {
            self.0 = ((self.0 << 5).overflowing_add(self.0))
                .0
                .overflowing_add(*byte as u64)
                .0; /* hash * 33 + c */
        }
    }
}

fn hash_child_layout_names(hasher: &mut Djb2, layout: &TypeLayout) {
    if let Some(fields) = layout.get_fields() {
        for a in fields {
            let layout = a.layout();
            layout.name().hash(hasher);
            hash_child_layout_names(hasher, layout);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{GenericServiceCollection, Registered};
    use abi_stable::{
        erased_types::TD_Opaque,
        sabi_trait,
        std_types::{RArc, RBox},
    };
    use std::ffi::CString;

    #[test]
    fn assert_valid_path() {
        let expected = CString::new("minfac::stable_abi::tests").unwrap();
        assert_eq!(
            expected.as_c_str(),
            <Foo as Identifyable<StableAbiTypeId>>::get_id().path
        );
    }

    #[sabi_trait]
    trait BarStableAbi {
        fn get_no(&self) -> i32;
    }

    #[derive(StableAbi)]
    #[repr(C)]
    struct Foo {
        no: RArc<i32>,
    }

    struct BarStableAbiImpl {
        no: i32,
    }
    impl BarStableAbi for BarStableAbiImpl {
        fn get_no(&self) -> i32 {
            self.no
        }
    }

    #[test]
    fn resolve_ffi_service() {
        let mut col = GenericServiceCollection::<StableAbiStrategy>::new();
        col.with::<Registered<BarStableAbi_TO<RArc<()>>>>()
            .register(|no| Foo {
                no: RArc::new(no.get_no()),
            });
        col.register(|| {
            BarStableAbi_TO::from_ptr(RArc::new(BarStableAbiImpl { no: 42 }), TD_Opaque)
        });
        let provider = col.build().expect("dependencies are ok");
        let foo: Option<Foo> = provider.get();
        assert_eq!(Some(42), foo.map(|x| *x.no))
    }

    #[test]
    fn should_raise_missing_dependency() {
        let mut col = GenericServiceCollection::<StableAbiStrategy>::new();
        col.with::<Registered<BarStableAbi_TO<RBox<()>>>>()
            .register(|no| Foo {
                no: RArc::new(no.get_no()),
            });
        col.register(|| {
            BarStableAbi_TO::from_ptr(RArc::new(BarStableAbiImpl { no: 42 }), TD_Opaque)
        });
        col.build().expect_err("should have missing dependency");
    }
}