use alloc::{sync::Arc, vec::Vec};
use core::{
any::{Any, TypeId},
marker::PhantomData,
};
use crate::{DeviceManagerError, DeviceManagerResult};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ServiceCardinality {
Single,
Multiple,
}
pub trait ServiceKey: Send + Sync + 'static {
type Service: ?Sized + Send + Sync + 'static;
const NAME: &'static str;
const CARDINALITY: ServiceCardinality;
}
struct TypedService<K: ServiceKey> {
service: Arc<K::Service>,
_key: PhantomData<K>,
}
struct ServiceEntry {
key: TypeId,
name: &'static str,
cardinality: ServiceCardinality,
service: Arc<dyn Any + Send + Sync>,
}
#[derive(Default)]
pub struct DeviceServices {
entries: Vec<ServiceEntry>,
}
impl DeviceServices {
pub const fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn provide<K: ServiceKey>(&mut self, service: Arc<K::Service>) -> DeviceManagerResult {
self.validate_entry::<K>()?;
let typed = Arc::new(TypedService::<K> {
service,
_key: PhantomData,
});
self.entries.push(ServiceEntry {
key: TypeId::of::<K>(),
name: K::NAME,
cardinality: K::CARDINALITY,
service: typed,
});
Ok(())
}
pub fn require<K: ServiceKey>(&self) -> DeviceManagerResult<Arc<K::Service>> {
if K::CARDINALITY == ServiceCardinality::Multiple {
return Err(DeviceManagerError::InvalidInput {
operation: "require device service",
detail: alloc::format!(
"service '{}' permits multiple providers; use all() instead",
K::NAME
),
});
}
self.entries
.iter()
.find(|entry| entry.key == TypeId::of::<K>())
.and_then(service_from_entry::<K>)
.ok_or_else(|| DeviceManagerError::ResourceNotFound {
operation: "require device service",
resource: K::NAME.into(),
})
}
pub fn all<K: ServiceKey>(&self) -> Vec<Arc<K::Service>> {
self.entries
.iter()
.filter(|entry| entry.key == TypeId::of::<K>())
.filter_map(service_from_entry::<K>)
.collect()
}
pub(crate) fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub(crate) fn validate_merge(&self, incoming: &Self) -> DeviceManagerResult {
for entry in &incoming.entries {
if entry.cardinality == ServiceCardinality::Single
&& self
.entries
.iter()
.any(|existing| existing.key == entry.key)
{
return Err(DeviceManagerError::ResourceConflict {
operation: "register device service",
detail: alloc::format!(
"single-provider service '{}' is already registered",
entry.name
),
});
}
if incoming
.entries
.iter()
.filter(|other| other.key == entry.key)
.count()
> 1
&& entry.cardinality == ServiceCardinality::Single
{
return Err(DeviceManagerError::ResourceConflict {
operation: "register device service",
detail: alloc::format!(
"device contribution registers service '{}' more than once",
entry.name
),
});
}
}
Ok(())
}
pub(crate) fn append(&mut self, incoming: Self) {
self.entries.extend(incoming.entries);
}
fn validate_entry<K: ServiceKey>(&self) -> DeviceManagerResult {
if K::CARDINALITY == ServiceCardinality::Single
&& self
.entries
.iter()
.any(|entry| entry.key == TypeId::of::<K>())
{
return Err(DeviceManagerError::ResourceConflict {
operation: "provide device service",
detail: alloc::format!(
"single-provider service '{}' is already registered",
K::NAME
),
});
}
Ok(())
}
}
fn service_from_entry<K: ServiceKey>(entry: &ServiceEntry) -> Option<Arc<K::Service>> {
entry
.service
.downcast_ref::<TypedService<K>>()
.map(|typed| Arc::clone(&typed.service))
}
#[cfg(test)]
mod tests {
use super::*;
trait TestService: Send + Sync {
fn value(&self) -> u32;
}
struct TestServiceKey;
impl ServiceKey for TestServiceKey {
type Service = dyn TestService;
const NAME: &'static str = "test-service";
const CARDINALITY: ServiceCardinality = ServiceCardinality::Single;
}
struct TestServiceProvider(u32);
impl TestService for TestServiceProvider {
fn value(&self) -> u32 {
self.0
}
}
#[test]
fn require_returns_service_by_typed_key() {
let mut services = DeviceServices::new();
let provider: Arc<dyn TestService> = Arc::new(TestServiceProvider(7));
services.provide::<TestServiceKey>(provider).unwrap();
assert_eq!(services.require::<TestServiceKey>().unwrap().value(), 7);
}
#[test]
fn single_provider_service_rejects_duplicate_registration() {
let mut services = DeviceServices::new();
services
.provide::<TestServiceKey>(Arc::new(TestServiceProvider(1)))
.unwrap();
assert!(matches!(
services.provide::<TestServiceKey>(Arc::new(TestServiceProvider(2))),
Err(DeviceManagerError::ResourceConflict {
operation: "provide device service",
..
})
));
}
}