use std::any::TypeId;
use std::fmt::{Debug, Display, Formatter};
use std::hash::{Hash, Hasher};
#[derive(Clone)]
pub enum ServiceKey {
Type(TypeId),
Named { type_id: TypeId, name: String },
}
impl ServiceKey {
pub fn of_type<T: 'static>() -> Self {
ServiceKey::Type(TypeId::of::<T>())
}
pub fn named<T: 'static>(name: impl Into<String>) -> Self {
ServiceKey::Named {
type_id: TypeId::of::<T>(),
name: name.into(),
}
}
pub fn type_id(&self) -> TypeId {
match self {
ServiceKey::Type(type_id) => *type_id,
ServiceKey::Named { type_id, .. } => *type_id,
}
}
pub fn name(&self) -> Option<&str> {
match self {
ServiceKey::Type(_) => None,
ServiceKey::Named { name, .. } => Some(name),
}
}
pub fn is_named(&self) -> bool {
matches!(self, ServiceKey::Named { .. })
}
pub fn type_name(&self) -> &'static str {
match self {
ServiceKey::Type(_type_id) => {
"Unknown"
}
ServiceKey::Named { .. } => "Unknown",
}
}
}
impl PartialEq for ServiceKey {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(ServiceKey::Type(a), ServiceKey::Type(b)) => a == b,
(
ServiceKey::Named {
type_id: a_type,
name: a_name,
},
ServiceKey::Named {
type_id: b_type,
name: b_name,
},
) => a_type == b_type && a_name == b_name,
_ => false,
}
}
}
impl Eq for ServiceKey {}
impl Hash for ServiceKey {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
ServiceKey::Type(type_id) => {
0u8.hash(state); type_id.hash(state);
}
ServiceKey::Named { type_id, name } => {
1u8.hash(state); type_id.hash(state);
name.hash(state);
}
}
}
}
impl Debug for ServiceKey {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ServiceKey::Type(type_id) => {
write!(f, "ServiceKey::Type({type_id:?})")
}
ServiceKey::Named { type_id, name } => {
write!(f, "ServiceKey::Named(type: {type_id:?}, name: '{name}')")
}
}
}
}
impl Display for ServiceKey {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ServiceKey::Type(_) => {
write!(f, "Type Service")
}
ServiceKey::Named { name, .. } => {
write!(f, "Named Service '{name}'")
}
}
}
}
#[macro_export]
macro_rules! service_key {
($type:ty) => {
$crate::ServiceKey::of_type::<$type>()
};
($type:ty, $name:expr) => {
$crate::ServiceKey::named::<$type>($name)
};
}
#[cfg(test)]
mod tests {
use super::*;
struct TestService;
struct AnotherService;
#[test]
fn test_type_key_creation() {
let key1 = ServiceKey::of_type::<TestService>();
let key2 = ServiceKey::of_type::<TestService>();
let key3 = ServiceKey::of_type::<AnotherService>();
assert_eq!(key1, key2);
assert_ne!(key1, key3);
assert!(!key1.is_named());
assert!(key1.name().is_none());
}
#[test]
fn test_named_key_creation() {
let key1 = ServiceKey::named::<TestService>("primary");
let key2 = ServiceKey::named::<TestService>("primary");
let key3 = ServiceKey::named::<TestService>("secondary");
let key4 = ServiceKey::named::<AnotherService>("primary");
assert_eq!(key1, key2);
assert_ne!(key1, key3);
assert_ne!(key1, key4);
assert!(key1.is_named());
assert_eq!(key1.name(), Some("primary"));
}
#[test]
fn test_type_vs_named_key() {
let type_key = ServiceKey::of_type::<TestService>();
let named_key = ServiceKey::named::<TestService>("test");
assert_ne!(type_key, named_key);
assert_eq!(type_key.type_id(), named_key.type_id());
}
#[test]
fn test_macro() {
let key1 = service_key!(TestService);
let key2 = service_key!(TestService, "named");
assert_eq!(key1, ServiceKey::of_type::<TestService>());
assert_eq!(key2, ServiceKey::named::<TestService>("named"));
}
}