use std::{any::TypeId, collections::BTreeMap, fmt::Debug};
use inventory::{Collect, Registry};
use thiserror::Error;
pub trait Factory<T: ?Sized> {
fn create(&self) -> Box<T>;
}
#[derive(Debug, Error)]
pub enum FactoryError {
#[error("未找到 ID 为 '{0}' 的工厂")]
FactoryNotFound(String),
#[error("不允许回退时提供了空 ID")]
EmptyIdNoFallback,
#[error("没有可用的工厂")]
NoFactoriesAvailable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FactoryFallback {
First,
Last,
NoFallback,
}
pub struct SimpleFactory<T: ?Sized + 'static>(
BTreeMap<&'static str, &'static (dyn Factory<T> + Sync)>,
);
impl<T> SimpleFactory<T>
where
T: ?Sized + 'static,
{
pub fn create<'a>(
&self,
id: &'a str,
strategy: FactoryFallback,
) -> Result<(&'a str, Box<T>), FactoryError> {
if !id.is_empty() {
return if let Some(factory) = self.0.get(id) {
Ok((id, factory.create()))
} else {
Err(FactoryError::FactoryNotFound(id.to_string()))
};
}
match strategy {
FactoryFallback::First => {
if let Some((id, factory)) = self.0.first_key_value() {
return Ok((id, factory.create()));
}
}
FactoryFallback::Last => {
if let Some((id, factory)) = self.0.last_key_value() {
return Ok((id, factory.create()));
}
}
FactoryFallback::NoFallback => return Err(FactoryError::EmptyIdNoFallback),
}
Err(FactoryError::NoFactoriesAvailable)
}
}
pub struct FactoryRegistry<T>
where
T: ?Sized + 'static,
{
id: &'static str,
factory: &'static (dyn Factory<T> + Sync),
type_id: TypeId,
}
impl<T> Collect for FactoryRegistry<T>
where
T: ?Sized + 'static,
{
fn registry() -> &'static Registry {
static REGISTRY: Registry = Registry::new();
®ISTRY
}
}
impl<T> FactoryRegistry<T>
where
T: ?Sized + 'static,
{
#[inline]
pub const fn new(id: &'static str, factory: &'static (dyn Factory<T> + Sync)) -> Self {
Self {
id,
factory,
type_id: TypeId::of::<T>(),
}
}
pub fn simple_factory() -> SimpleFactory<T> {
let type_id = TypeId::of::<T>();
let factories = inventory::iter::<Self>()
.filter_map(|reg| (type_id == reg.type_id).then_some((reg.id, reg.factory)))
.collect();
SimpleFactory(factories)
}
}
#[macro_export]
macro_rules! register_factory {
($product:ty, $id:literal, $implement:ty) => {
$crate::const_assert!(!$id.is_empty());
$crate::assert_impl_one!($implement: Default);
const _: () = {
struct ConcreteFactory;
impl $crate::Factory<$product> for ConcreteFactory {
fn create(&self) -> Box<$product> {
Box::<$implement>::default()
}
}
$crate::submit! {
$crate::FactoryRegistry::new(
$id,
&ConcreteFactory as &'static (dyn $crate::Factory<$product> + Sync),
)
}
};
};
}