use std::{any::Any, collections::HashMap, sync::Arc};
use crate::{Capsule, CapsuleId, ContainerWriteTxn, CreateCapsuleId};
pub struct CapsuleReader<'scope, 'total>(InternalCapsuleReader<'scope, 'total>);
enum InternalCapsuleReader<'scope, 'total> {
Normal {
id: CapsuleId,
txn: &'scope mut ContainerWriteTxn<'total>,
},
Mock {
mocks: HashMap<CapsuleId, Arc<dyn Any + Send + Sync>>,
},
}
impl<'scope, 'total> CapsuleReader<'scope, 'total> {
pub(crate) fn new(id: CapsuleId, txn: &'scope mut ContainerWriteTxn<'total>) -> Self {
Self(InternalCapsuleReader::Normal { id, txn })
}
pub fn as_ref<C: Capsule>(&mut self, capsule: C) -> &C::Data {
match &mut self.0 {
InternalCapsuleReader::Normal { ref id, txn } => {
let (this, other) = (id, capsule.id());
if this == &other {
return txn.try_read_ref(&capsule).unwrap_or_else(|| {
let name = std::any::type_name::<C>();
panic!(
"{name} ({id:?}) tried to read itself on its first build! {} {} {}",
"This is disallowed since the capsule doesn't have data to read yet.",
"To avoid this issue, wrap the `get()` call in an if statement",
"with the builtin \"is_first_build\" side effect."
);
});
}
txn.ensure_initialized(capsule);
txn.add_dependency_relationship(&other, this);
txn.try_read_ref_raw::<C>(&other)
.expect("Ensured capsule was initialized above")
}
InternalCapsuleReader::Mock { mocks } => {
let id = capsule.id();
mocks.get(&id).map_or_else(
|| {
panic!(
"Mock CapsuleReader was used to read {} ({id:?}) {}",
std::any::type_name::<C>(),
"when it was not included in the mock!"
);
},
crate::downcast_capsule_data::<C>,
)
}
}
}
}
#[cfg(feature = "experimental-api")]
impl<A: Capsule> FnOnce<(A,)> for CapsuleReader<'_, '_>
where
A::Data: Clone,
{
type Output = A::Data;
extern "rust-call" fn call_once(mut self, args: (A,)) -> Self::Output {
self.call_mut(args)
}
}
#[cfg(feature = "experimental-api")]
impl<A: Capsule> FnMut<(A,)> for CapsuleReader<'_, '_>
where
A::Data: Clone,
{
extern "rust-call" fn call_mut(&mut self, args: (A,)) -> Self::Output {
self.as_ref(args.0).clone()
}
}
#[derive(Clone, Default)]
pub struct MockCapsuleReaderBuilder(HashMap<CapsuleId, Arc<dyn Any + Send + Sync>>);
impl MockCapsuleReaderBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn set<C: Capsule>(mut self, capsule: &C, data: C::Data) -> Self {
self.0.insert(capsule.id(), Arc::new(data));
self
}
#[must_use]
pub fn build(self) -> CapsuleReader<'static, 'static> {
CapsuleReader(InternalCapsuleReader::Mock { mocks: self.0 })
}
}
#[cfg(test)]
mod tests {
use crate::{CapsuleHandle, CapsuleReader, MockCapsuleReaderBuilder};
fn foo_capsule(_: CapsuleHandle) -> u8 {
0
}
fn bar_capsule(_: CapsuleHandle) -> Box<dyn Send + Sync + Fn() -> u8> {
Box::new(|| 0)
}
fn another_capsule(_: CapsuleHandle) -> u8 {
0
}
fn create_mock_capsule_reader() -> CapsuleReader<'static, 'static> {
MockCapsuleReaderBuilder::new()
.set(&foo_capsule, 123)
.set(&bar_capsule, Box::new(|| 123))
.build()
}
#[test]
fn mock_capsule_reader_reads_capsules() {
let mut get = create_mock_capsule_reader();
assert_eq!(*get.as_ref(foo_capsule), 123);
assert_eq!(get.as_ref(bar_capsule)(), 123);
drop(get);
}
#[test]
#[allow(clippy::should_panic_without_expect)] #[should_panic]
fn mock_capsule_reader_panics_on_unmocked_capsule() {
create_mock_capsule_reader().as_ref(another_capsule);
}
}