#[cfg(feature = "async")]
use std::future::Future;
#[cfg(feature = "async")]
use std::pin::Pin;
#[cfg(feature = "interface")]
use std::sync::Arc;
pub trait ModuleMeta: 'static {
const NAME: &'static str;
fn dependencies() -> &'static [(&'static str, std::any::TypeId)];
}
pub trait AutoBuilder: ModuleMeta {
type Capability: Clone + 'static;
type Error: std::error::Error + Send + 'static;
fn build(kit: &crate::kit::Kit) -> Result<Self::Capability, Self::Error>;
}
#[cfg(feature = "interface")]
pub trait Interface: 'static {}
#[cfg(feature = "interface")]
impl<T: ?Sized + 'static> Interface for T {}
#[cfg(feature = "interface")]
pub trait InterfaceBuilder: ModuleMeta {
type Interface: ?Sized + 'static;
type Capability: Clone + 'static;
type Error: std::error::Error + Send + 'static;
fn build(kit: &crate::kit::Kit) -> Result<Self::Capability, Self::Error>;
fn into_interface(cap: Self::Capability) -> Arc<Self::Interface>;
}
#[cfg(feature = "async")]
pub trait AsyncAutoBuilder: ModuleMeta {
type Capability: Clone + Send + Sync + 'static;
type Error: std::error::Error + Send + 'static;
#[allow(
clippy::type_complexity,
reason = "Pin<Box<dyn Future + Send>> is the canonical dyn-compatible async trait dispatch type"
)]
fn build<'a>(
kit: &'a crate::kit::AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>>;
}
pub(crate) type BuildFn = Box<
dyn FnOnce(
&crate::kit::Kit,
) -> Result<Box<dyn std::any::Any>, Box<dyn std::error::Error + Send + 'static>>,
>;
#[cfg(all(test, feature = "async"))]
mod async_tests {
use super::*;
use crate::kit::AsyncKit;
use crate::test_helpers::{MockError, block_on};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq)]
struct LoggerCapability {
name: String,
}
struct MockLoggerModule;
impl ModuleMeta for MockLoggerModule {
const NAME: &'static str = "mock-logger";
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for MockLoggerModule {
type Capability = Arc<LoggerCapability>;
type Error = MockError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>>
{
let _ = kit;
Box::pin(async move {
Ok(Arc::new(LoggerCapability {
name: "mock".to_string(),
}))
})
}
}
#[test]
fn async_auto_builder_returns_pin_box_future() {
let kit = AsyncKit::new();
let fut = MockLoggerModule::build(&kit);
let result = block_on(fut);
assert!(result.is_ok());
let cap = result.expect("build future returned Ok");
assert_eq!(cap.name, "mock");
}
#[test]
fn async_auto_builder_capability_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<LoggerCapability>();
assert_send_sync::<Arc<LoggerCapability>>();
}
#[test]
fn async_auto_builder_error_is_send_static() {
fn assert_send_static<T: Send + 'static>() {}
assert_send_static::<MockError>();
}
}
#[cfg(all(test, feature = "interface"))]
mod interface_tests {
use super::*;
#[test]
fn interface_auto_implemented_for_primitive_types() {
fn assert_interface<T: Interface>() {}
assert_interface::<i32>();
assert_interface::<u64>();
assert_interface::<String>();
assert_interface::<Vec<u8>>();
assert_interface::<bool>();
}
#[test]
fn interface_auto_implemented_for_custom_types() {
struct MyType;
#[allow(dead_code)]
enum MyEnum {
A,
B,
}
fn assert_interface<T: Interface>() {}
assert_interface::<MyType>();
assert_interface::<MyEnum>();
}
#[test]
fn interface_auto_implemented_for_reference_types() {
trait MyTrait {}
fn assert_interface<T: Interface + ?Sized>() {}
assert_interface::<dyn MyTrait>();
}
}
#[cfg(all(test, feature = "interface"))]
mod interface_builder_tests {
use super::*;
use crate::kit::Kit;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
trait Logger: 'static {
fn log(&self, msg: &str);
}
struct ConsoleLogger {
counter: AtomicUsize,
}
impl Logger for ConsoleLogger {
fn log(&self, msg: &str) {
let _ = msg;
self.counter.fetch_add(1, Ordering::Relaxed);
}
}
#[derive(Debug)]
struct InterfaceTestError;
impl std::fmt::Display for InterfaceTestError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "interface test error")
}
}
impl std::error::Error for InterfaceTestError {}
struct ConsoleLoggerModule;
impl ModuleMeta for ConsoleLoggerModule {
const NAME: &'static str = "console-logger";
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl InterfaceBuilder for ConsoleLoggerModule {
type Interface = dyn Logger;
type Capability = Arc<ConsoleLogger>;
type Error = InterfaceTestError;
fn build(_kit: &Kit) -> Result<Arc<ConsoleLogger>, InterfaceTestError> {
Ok(Arc::new(ConsoleLogger {
counter: AtomicUsize::new(0),
}))
}
fn into_interface(cap: Arc<ConsoleLogger>) -> Arc<dyn Logger> {
cap
}
}
#[test]
fn interface_builder_build_returns_concrete_capability() {
let kit = Kit::new();
let cap = ConsoleLoggerModule::build(&kit).expect("build succeeds");
assert_eq!(Arc::strong_count(&cap), 1);
}
#[test]
fn interface_builder_into_interface_produces_trait_object() {
let kit = Kit::new();
let cap = ConsoleLoggerModule::build(&kit).expect("build succeeds");
let iface: Arc<dyn Logger> = ConsoleLoggerModule::into_interface(cap);
iface.log("hello");
iface.log("world");
}
#[test]
fn interface_builder_interface_type_is_dyn_compatible() {
fn assert_dyn_compatible<T: ?Sized + 'static>() {}
assert_dyn_compatible::<dyn Logger>();
}
#[test]
fn interface_builder_capability_is_clone() {
let cap = Arc::new(ConsoleLogger {
counter: AtomicUsize::new(0),
});
let cloned = cap.clone();
assert_eq!(Arc::strong_count(&cloned), 2);
drop(cap);
assert_eq!(Arc::strong_count(&cloned), 1);
}
#[test]
fn interface_builder_does_not_require_autobuilder() {
fn requires_interface_builder<T: InterfaceBuilder>() {}
requires_interface_builder::<ConsoleLoggerModule>();
}
}