use crate::container::descriptor::ServiceId;
use std::any::TypeId;
use std::marker::PhantomData;
pub trait ServiceToken: Send + Sync + 'static {
type Service: ?Sized + Send + Sync + 'static;
fn service_type_id() -> TypeId
where
Self::Service: 'static,
{
TypeId::of::<Self::Service>()
}
fn service_type_name() -> &'static str {
std::any::type_name::<Self::Service>()
}
fn token_type_name() -> &'static str {
std::any::type_name::<Self>()
}
}
#[derive(Debug, Clone)]
pub struct TokenBinding {
pub token_type_id: TypeId,
pub token_type_name: &'static str,
pub service_type_id: TypeId,
pub service_type_name: &'static str,
pub impl_type_id: TypeId,
pub impl_type_name: &'static str,
pub name: Option<String>,
}
impl TokenBinding {
pub fn new<Token, Impl>() -> Self
where
Token: ServiceToken,
Impl: Send + Sync + 'static,
{
Self {
token_type_id: TypeId::of::<Token>(),
token_type_name: std::any::type_name::<Token>(),
service_type_id: Token::service_type_id(),
service_type_name: Token::service_type_name(),
impl_type_id: TypeId::of::<Impl>(),
impl_type_name: std::any::type_name::<Impl>(),
name: None,
}
}
pub fn named<Token, Impl>(name: impl Into<String>) -> Self
where
Token: ServiceToken,
Impl: Send + Sync + 'static,
{
let mut binding = Self::new::<Token, Impl>();
binding.name = Some(name.into());
binding
}
pub fn to_service_id(&self) -> ServiceId {
if let Some(name) = &self.name {
ServiceId::named_by_ids(self.service_type_id, self.service_type_name, name.clone())
} else {
ServiceId::by_ids(self.service_type_id, self.service_type_name)
}
}
pub fn matches_token<Token: ServiceToken>(&self) -> bool {
self.token_type_id == TypeId::of::<Token>()
}
pub fn validate_implementation<Token, Impl>() -> Result<(), String>
where
Token: ServiceToken,
Impl: Send + Sync + 'static,
{
let token_service_id = Token::service_type_id();
let impl_type_id = TypeId::of::<Impl>();
if token_service_id == TypeId::of::<()>() {
return Err(format!(
"Invalid service type for token {}: service type appears to be ()",
Token::token_type_name()
));
}
if token_service_id == impl_type_id {
return Err(format!(
"Token {} maps to itself: implementation type {} cannot be the same as service type {}",
Token::token_type_name(),
std::any::type_name::<Impl>(),
Token::service_type_name()
));
}
let token_name = Token::token_type_name();
let service_name = Token::service_type_name();
let impl_name = std::any::type_name::<Impl>();
if token_name.is_empty() {
return Err("Invalid token: token type name is empty".to_string());
}
if service_name.is_empty() {
return Err(format!(
"Invalid token {}: service type name is empty",
token_name
));
}
if impl_name.is_empty() {
return Err(format!(
"Invalid implementation for token {}: implementation type name is empty",
token_name
));
}
if service_name.contains("dyn ") && impl_name.contains("dyn ") {
return Err(format!(
"Invalid binding for token {}: both service ({}) and implementation ({}) appear to be trait objects. Implementation should be a concrete type.",
token_name,
service_name,
impl_name
));
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct TokenRegistry {
bindings: std::collections::HashMap<TypeId, Vec<TokenBinding>>,
defaults: std::collections::HashMap<TypeId, TokenBinding>,
named: std::collections::HashMap<(TypeId, String), TokenBinding>,
}
impl TokenRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register<Token, Impl>(&mut self) -> Result<(), String>
where
Token: ServiceToken,
Impl: Send + Sync + 'static,
{
TokenBinding::validate_implementation::<Token, Impl>()?;
let binding = TokenBinding::new::<Token, Impl>();
let token_type_id = TypeId::of::<Token>();
self.bindings
.entry(token_type_id)
.or_default()
.push(binding.clone());
self.defaults.entry(token_type_id).or_insert(binding);
Ok(())
}
pub fn register_named<Token, Impl>(&mut self, name: impl Into<String>) -> Result<(), String>
where
Token: ServiceToken,
Impl: Send + Sync + 'static,
{
TokenBinding::validate_implementation::<Token, Impl>()?;
let name = name.into();
let binding = TokenBinding::named::<Token, Impl>(&name);
let token_type_id = TypeId::of::<Token>();
self.bindings
.entry(token_type_id)
.or_default()
.push(binding.clone());
self.named.insert((token_type_id, name), binding);
Ok(())
}
pub fn get_default<Token: ServiceToken>(&self) -> Option<&TokenBinding> {
let token_type_id = TypeId::of::<Token>();
self.defaults.get(&token_type_id)
}
pub fn get_named<Token: ServiceToken>(&self, name: &str) -> Option<&TokenBinding> {
let token_type_id = TypeId::of::<Token>();
self.named.get(&(token_type_id, name.to_string()))
}
pub fn get_all<Token: ServiceToken>(&self) -> Option<&Vec<TokenBinding>> {
let token_type_id = TypeId::of::<Token>();
self.bindings.get(&token_type_id)
}
pub fn contains<Token: ServiceToken>(&self) -> bool {
let token_type_id = TypeId::of::<Token>();
self.defaults.contains_key(&token_type_id)
}
pub fn contains_named<Token: ServiceToken>(&self, name: &str) -> bool {
let token_type_id = TypeId::of::<Token>();
self.named.contains_key(&(token_type_id, name.to_string()))
}
pub fn token_types(&self) -> Vec<TypeId> {
self.defaults.keys().cloned().collect()
}
pub fn stats(&self) -> TokenRegistryStats {
TokenRegistryStats {
total_tokens: self.bindings.len(), total_bindings: self.bindings.values().map(|v| v.len()).sum(),
named_bindings: self.named.len(),
}
}
pub fn validate_all_bindings(&self) -> Vec<String> {
let mut errors = Vec::new();
for (token_type_id, bindings) in &self.bindings {
if bindings.is_empty() {
errors.push(format!(
"Token type {:?} has empty bindings list",
token_type_id
));
continue;
}
let first_binding = &bindings[0];
for binding in bindings.iter().skip(1) {
if binding.token_type_id != first_binding.token_type_id {
errors.push(format!(
"Inconsistent token type IDs in bindings for token type {:?}",
token_type_id
));
}
if binding.service_type_id != first_binding.service_type_id {
errors.push(format!(
"Inconsistent service type IDs for token {}: {} vs {}",
binding.token_type_name,
binding.service_type_name,
first_binding.service_type_name
));
}
}
for binding in bindings {
if let Some(name) = &binding.name {
if !self
.named
.contains_key(&(binding.token_type_id, name.clone()))
{
errors.push(format!(
"Named binding {} for token {} exists in bindings list but not in named map",
name,
binding.token_type_name
));
}
}
}
}
for ((token_type_id, name), binding) in &self.named {
if !self.bindings.contains_key(token_type_id) {
errors.push(format!(
"Named binding {} for token {} exists in named map but token has no bindings",
name, binding.token_type_name
));
}
}
errors
}
pub fn has_binding_conflicts<Token: ServiceToken>(&self) -> Vec<String> {
let mut conflicts = Vec::new();
let token_type_id = TypeId::of::<Token>();
if let Some(bindings) = self.bindings.get(&token_type_id) {
let default_count = self.defaults.contains_key(&token_type_id) as usize;
if default_count == 0 && !bindings.is_empty() {
conflicts.push(format!(
"Token {} has bindings but no default binding",
Token::token_type_name()
));
}
let mut seen_names = std::collections::HashSet::new();
for binding in bindings {
if let Some(name) = &binding.name {
if !seen_names.insert(name.clone()) {
conflicts.push(format!(
"Token {} has duplicate named binding: {}",
Token::token_type_name(),
name
));
}
}
}
}
conflicts
}
pub fn get_token_info<Token: ServiceToken>(&self) -> Option<TokenInfo> {
let token_type_id = TypeId::of::<Token>();
let bindings = self.bindings.get(&token_type_id)?;
let default_binding = self.defaults.get(&token_type_id);
let named_bindings: Vec<String> = self
.named
.iter()
.filter_map(|((tid, name), _)| {
if *tid == token_type_id {
Some(name.clone())
} else {
None
}
})
.collect();
Some(TokenInfo {
token_name: Token::token_type_name().to_string(),
service_name: Token::service_type_name().to_string(),
total_bindings: bindings.len(),
has_default: default_binding.is_some(),
named_bindings,
implementation_types: bindings
.iter()
.map(|b| b.impl_type_name.to_string())
.collect(),
})
}
#[cfg(test)]
pub fn clear(&mut self) {
self.bindings.clear();
self.defaults.clear();
self.named.clear();
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenRegistryStats {
pub total_tokens: usize,
pub total_bindings: usize,
pub named_bindings: usize,
}
#[derive(Debug, Clone)]
pub struct TokenInfo {
pub token_name: String,
pub service_name: String,
pub total_bindings: usize,
pub has_default: bool,
pub named_bindings: Vec<String>,
pub implementation_types: Vec<String>,
}
pub trait TokenReference {
type Token: ServiceToken;
fn token_type() -> PhantomData<Self::Token> {
PhantomData
}
}
impl<T: ServiceToken> TokenReference for &T {
type Token = T;
}
#[cfg(test)]
mod tests {
use super::*;
trait TestService: Send + Sync {
#[allow(dead_code)]
fn test(&self) -> String;
}
struct TestToken;
impl ServiceToken for TestToken {
type Service = dyn TestService;
}
struct TestImpl;
impl TestService for TestImpl {
fn test(&self) -> String {
"test".to_string()
}
}
#[test]
fn test_service_token_trait() {
assert_eq!(
TestToken::token_type_name(),
"elif_core::container::tokens::tests::TestToken"
);
assert_eq!(
TestToken::service_type_name(),
"dyn elif_core::container::tokens::tests::TestService"
);
}
#[test]
fn test_token_binding_creation() {
let binding = TokenBinding::new::<TestToken, TestImpl>();
assert_eq!(binding.token_type_id, TypeId::of::<TestToken>());
assert_eq!(binding.service_type_id, TypeId::of::<dyn TestService>());
assert_eq!(binding.impl_type_id, TypeId::of::<TestImpl>());
assert!(binding.name.is_none());
}
#[test]
fn test_named_token_binding() {
let binding = TokenBinding::named::<TestToken, TestImpl>("primary");
assert_eq!(binding.name, Some("primary".to_string()));
assert!(binding.matches_token::<TestToken>());
}
#[test]
fn test_token_registry_basic() {
let mut registry = TokenRegistry::new();
assert!(!registry.contains::<TestToken>());
registry.register::<TestToken, TestImpl>().unwrap();
assert!(registry.contains::<TestToken>());
let binding = registry.get_default::<TestToken>().unwrap();
assert!(binding.matches_token::<TestToken>());
}
#[test]
fn test_token_registry_named() {
let mut registry = TokenRegistry::new();
registry
.register_named::<TestToken, TestImpl>("primary")
.unwrap();
assert!(registry.contains_named::<TestToken>("primary"));
assert!(!registry.contains_named::<TestToken>("secondary"));
let binding = registry.get_named::<TestToken>("primary").unwrap();
assert_eq!(binding.name, Some("primary".to_string()));
}
#[test]
fn test_token_registry_stats() {
let mut registry = TokenRegistry::new();
registry.register::<TestToken, TestImpl>().unwrap();
registry
.register_named::<TestToken, TestImpl>("primary")
.unwrap();
let stats = registry.stats();
assert_eq!(stats.total_tokens, 1);
assert_eq!(stats.total_bindings, 2);
assert_eq!(stats.named_bindings, 1);
}
#[test]
fn test_token_reference() {
let _phantom: PhantomData<TestToken> = <&TestToken as TokenReference>::token_type();
}
}