| Crates.io | xdi |
| lib.rs | xdi |
| version | 0.2.2 |
| created_at | 2025-01-19 09:54:41.214771+00 |
| updated_at | 2025-08-01 14:48:32.131299+00 |
| description | Rust di containers system |
| homepage | https://github.com/Vidrochka/xdi |
| repository | https://github.com/Vidrochka/xdi |
| max_upload_size | |
| id | 1522713 |
| size | 111,025 |
Simple service dependency graph container implementation
Allow resolve nested service dependency graph
Support Transient
Support Singletone
Support Task local (singletone in task scope)
Support Thread local (singletone in thread scope)
Allow to map service into any other representation as simple like .map_as(|service| SomeOther { x: service.x })
Allow to map service into trait object as siple like .map_as_trait::<dyn SomeTrait>()
Resolve single (first) service by self or by any mapping
Resolve all service wich has requested representation, usefull for trait object
Non blocking for transient, single lock for singletone/task_local/thread_local init
Allow !Send + !Sync for transient and thread_local
Readable errors
Simple architecture (constructor -> scope -> mapping)
Allow global ServiceProvider registration
Main test cases allowed in tests folder
use xdi::builder::DiBuilder;
use std::sync::{Arc, Mutex};
pub trait ISomeTrait {
fn get(&self) -> String;
}
pub struct SomeService {
pub payload: String
}
pub struct SomeServiceDeep {
pub nested_service: Arc<Mutex<SomeService>>
}
impl ISomeTrait for SomeServiceDeep {
fn get(&self) -> String {
self.nested_service.lock().unwrap().payload.clone()
}
}
pub struct SomeServiceDeeper {
pub nested_service: SomeServiceDeep
}
fn main() {
let builder = DiBuilder::new();
// register singletone
builder.singletone(|_| Ok(Arc::new(Mutex::new(SomeService { payload: "1".to_string() }))));
// register transient
builder.transient(|sp| Ok(SomeServiceDeeper { nested_service: sp.resolve()? }));
// register transient with mapping to trait
builder.transient(|sp| Ok(SomeServiceDeep { nested_service: sp.resolve()? }))
.map_as_trait::<dyn ISomeTrait>();
let sp = builder.build();
// automaticaly resolve all service dependency graph
// SomeServiceDeeper -> SomeServiceDeep -> Arc<Mutex<SomeService>>
let service = sp.resolve::<SomeServiceDeeper>().unwrap();
assert_eq!(service.nested_service.nested_service.lock().unwrap().payload, "1");
// change inner singletone
service.nested_service.nested_service.lock().unwrap().payload = "2".to_string();
// resolve dependency second time
// new SomeServiceDeeper and SomeServiceDeep, but old Arc<Mutex<SomeService>>
let service = sp.resolve::<SomeServiceDeeper>().unwrap();
assert_eq!(service.nested_service.nested_service.lock().unwrap().payload, "2");
// SomeServiceDeep also allowed as mapping into Box<dyn ISomeTrait>
let service = sp.resolve::<Box<dyn ISomeTrait>>().unwrap();
assert_eq!(service.get(), "2");
}
Create container builder
let builder = DiBuilder::new();
// or
let builder = DiBuilder::default();
pub struct DbConnection {}
pub struct Repository {
conn: DbConnection,
}
builder.transient(|_sp: ServiceProvider| Ok(DbConnection {}));
builder.transient(|sp: ServiceProvider| Ok(Repository {
conn: sp.resolve::<DbConnection>()?,
}));
#[derive(Clone)]
pub struct SomeService {}
builder.singletone(|_sp: ServiceProvider| Ok(SomeService {
//... some initialization
}));
#[cfg(feature = "task-local")]
{
#[derive(Clone)]
pub struct SomeService {}
builder.task_local(|_sp: ServiceProvider| Ok(SomeService {
//... some initialization
}));
}
#[derive(Clone)]
pub struct SomeService {}
builder.thread_local(|_sp: ServiceProvider| Ok(SomeService {
//... some initialization
}));
You can inject service as fn constructor
All type ctors will be automatically registered on builder.inject() call
Injection use inventory, so you can add injection from dependency crate
pub struct SomeService {}
trait ISomeService1 {}
impl ISomeService1 for SomeService {}
trait ISomeService2 {}
impl ISomeService2 for SomeService {}
// As transient (for transient scope param can be omitted)
#[xdi_macro::register_constructor(scope = "transient")]
fn some_service_ctor(_sp: ServiceProvider) -> ServiceBuildResult<SomeService> {
Ok(SomeService{})
}
// As singleton
#[xdi_macro::register_constructor(scope = "singleton")]
fn some_service_ctor(_sp: ServiceProvider) -> ServiceBuildResult<SomeService> {
Ok(SomeService{})
}
// As thread local
#[xdi_macro::register_constructor(scope = "thread_local")]
fn some_service_ctor(_sp: ServiceProvider) -> ServiceBuildResult<SomeService> {
Ok(SomeService{})
}
// As task local
#[xdi_macro::register_constructor(scope = "task_local")]
fn some_service_ctor(_sp: ServiceProvider) -> ServiceBuildResult<SomeService> {
Ok(SomeService{})
}
// For every scope you can define multiple trait map
#[xdi_macro::register_constructor(scope = "transient", map = [ISomeService1, ISomeService2])]
fn some_service_ctor() {
Ok(SomeService{})
}
fn main() {
let builder = DiBuilder::new();
// inject for automatically type registration
builder.inject();
let sp = builder.build();
let service = sp.resolve::<SomeService>().unwrap();
}
pub struct DbConnectionInner {}
pub struct DbConnectionPool {}
impl DbConnectionPool {
fn get(&self) -> DbConnectionInner { DbConnectionInner {} }
}
pub struct DbConnection {
conn: DbConnectionInner,
}
builder.transient(|_sp: ServiceProvider| Ok(DbConnectionPool {
//... some initialization
}))
.map_as(|pool| Ok(DbConnection { conn: pool.get() }));
Box<dyn ISomeTrait> if service impl ISomeTraitbuilder.transient(|_sp: ServiceProvider| Ok(SomeService {
//... some initialization
}))
.map_as_trait::<dyn ISomeTrait>();
let sp = builder.build();
builder.build_global();
// then access by static global var
let service = ServiceProvider::get().unwrap().resolve::<SomeService>().unwrap();
let service: SomeService = sp.resolve().unwrap();
// let service: Box<dyn ISomeTrait> = sp.resolve().unwrap();
use xdi::types::type_info::TypeInfoSource;
let service = sp.resolve_raw(SomeService::type_info()).unwrap();
// let service = sp.resolve(Box::<dyn ISomeTrait>::type_info()).unwrap();
let service = service.unbox::<SomeService>().unwrap();
// let service = service.unbox::<Box<dyn ISomeTrait>>().unwrap();
builder.transient(|_| Ok(SomeService {}))
.map_as_trait::<dyn ISomeTrait>();
builder.transient(|_| Ok(OtherService {}))
.map_as_trait::<dyn ISomeTrait>();
let services: Vec<Box<dyn ISomeTrait>> = sp.resolve_all().unwrap();
use xdi::types::{type_info::TypeInfoSource, boxed_service::BoxedService};
builder.transient(|_| Ok(SomeService {}))
.map_as_trait::<dyn ISomeTrait>();
builder.transient(|_| Ok(OtherService {}))
.map_as_trait::<dyn ISomeTrait>();
let services: Vec<BoxedService> = sp.resolve_all_raw(Box::<dyn ISomeTrait>::type_info()).unwrap();
#[cfg(feature = "task-local")]
{
use xdi::IAsyncTaskScope;
builder.task_local(|_| Ok(SomeService {}));
let sp = builder.build();
let sp2 = sp.clone();
tokio::spawn(async move {
let service = sp.resolve::<SomeService>().unwrap();
// In second time resolve return instanse clone (like singletone)
let service = sp.resolve::<SomeService>().unwrap();
}.add_service_span());
tokio::spawn(async move {
// New task has own SomeService instance
let service = sp2.resolve::<SomeService>().unwrap();
}.add_service_span());
}