# dilib-rs
[![Crates.io][crates-badge]][crates-link]
[![License][license-badge]][license-link]
[![Docs][docs-badge]][docs-link]
[![Github-Actions][ci-badge]][ci-link]
[crates-badge]: https://img.shields.io/crates/v/dilib.svg
[crates-link]: https://crates.io/crates/dilib
[license-badge]: https://img.shields.io/badge/LICENSE-MIT-green.svg
[license-link]: https://github.com/Neo-Ciber94/dilib-rs/blob/master/LICENSE
[docs-badge]: https://img.shields.io/badge/docs-dilib-blue.svg
[docs-link]: https://docs.rs/dilib/latest/dilib/
[ci-badge]: https://github.com/Neo-Ciber94/dilib-rs/actions/workflows/ci.yml/badge.svg
[ci-link]: https://github.com/Neo-Ciber94/dilib-rs/actions
A dependency injection library for Rust.
## Usage
```toml
[dependencies]
dilib = "0.2.0"
```
## Example
### Basic Usage
```rust
use dilib::Container;
struct Printer;
impl Printer {
pub fn print(&self, s: &str) {
println!("{}", s);
}
}
struct EnglishGreeting;
impl EnglishGreeting {
pub fn greet(&self) -> String {
"Hello!".to_string()
}
}
struct SpanishGreeting;
impl SpanishGreeting {
pub fn greet(&self) -> String {
"Hola!".to_string()
}
}
let mut container = Container::new();
container.add_singleton(Printer).unwrap();
container.add_scoped(|| EnglishGreeting).unwrap();
container.add_scoped_with_name("es", || SpanishGreeting).unwrap();
let printer = container.get::<Printer>().unwrap();
let en = container.get::<EnglishGreeting>().unwrap();
let es = container.get_with_name::<SpanishGreeting>("es").unwrap();
printer.print(&en.greet());
printer.print(&es.greet());
```
## Table of Contents
- [Container](#container)
- [Scoped provider](#scoped-provider)
- [Singleton provider](#singleton-provider)
- [Inject trait](#inject-trait)
- [Bind trait to implementation](#bind-trait-to-implementation)
- [get, get_scoped and get_singleton](#get-get_scoped-and-get_singleton)
- [Derive Inject](#derive-inject)
- [Global Container](#global-container)
- [Provide](#provide)
- [Why 'unstable_provide'?](#why-unstable_provide)
- [provide macro](#provide-macro)
## Container
The container is the main storage for the 2 types of provides:
- `Scoped`: creates a new instance each time
- `Singleton`: returns the same instance each time
All these providers can be named using the methods ended with `with_name(...)`.
### Scoped provider
The scoped providers creates a new instance each time they are called.
```rust
use dilib::Container;
let mut container = Container::new();
container.add_scoped(|| String::from("Apple Pie")).unwrap();
let s = container.get::<String>().unwrap();
assert_eq!(s.as_ref(), "Apple Pie");
```
### Singleton provider
The singleton providers returns the same instance each time they are called.
```rust
use dilib::Container;
use std::sync::Mutex;
let mut container = Container::new();
container.add_singleton(Mutex::new(0)).unwrap();
{
let c1 = container.get::<Mutex<i32>>().unwrap();
*c1.lock().unwrap() = 3;
}
let c2 = container.get::<Mutex<i32>>().unwrap();
assert_eq!(*c2.lock().unwrap(), 3);
```
### Inject trait
The `Inject` trait is a mechanism to create a type using the
providers of a container.
To add a type that implements `Inject` to the container,
you use the `add_deps` methods, this adds the type as a `Scoped` provider.
```rust
use std::sync::{Mutex, atomic::AtomicUsize};
use dilib::{Container, Inject};
struct IdGenerator(AtomicUsize);
impl IdGenerator {
pub fn next(&self) -> usize {
1 + self.0.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
}
}
#[derive(Clone, Debug)]
struct Fruit {
id: usize,
tag: String
}
impl Inject for Fruit {
fn inject(container: &Container) -> Self {
let generator = container.get::<IdGenerator>().unwrap();
let id = generator.next();
let tag = container.get_with_name::<String>("fruit").unwrap().cloned();
Fruit { id, tag }
}
}
let mut container = Container::new();
container.add_singleton(IdGenerator(AtomicUsize::new(0))).unwrap();
container.add_scoped_with_name("fruit", || String::from("b18ap31")).unwrap();
container.add_deps::<Fruit>().unwrap();
let f1 = container.get::<Fruit>().unwrap();
let f2 = container.get::<Fruit>().unwrap();
assert_eq!(f1.id, 1);
assert_eq!(f1.tag, "b18ap31");
assert_eq!(f2.id, 2);
assert_eq!(f2.tag, "b18ap31");
```
### Bind trait to implementation
To add a trait to a container you should
bind the trait to its implementation using the macros:
- `add_scoped_trait!(container, name, trait => impl)`
- `add_singleton_trait!(container, name, trait => impl)`
- `add_scoped_trait!(container, name, trait @ Inject)`
- `add_singleton_trait!(container, name, trait @ Inject)`
> The `name` is optional.
>
This adds the trait as a `Box<dyn Trait>`.
And you can get the values back using:
- `get_scoped_trait!(container, name, trait)`
- `get_singleton_trait!(container, name, trait)`
- `get_resolved_trait(container, name, trait)`
> The `name` is also optional.
>
This returns the trait as a `Box<dyn Trait>`.
```rust
use dilib::{
Container,
add_scoped_trait,
add_singleton_trait,
get_resolved_trait,
};
trait Discount {
fn get_discount(&self) -> f32;
}
trait Fruit {
fn name(&self) -> &str;
fn price(&self) -> f32;
}
struct TenPercentDiscount;
impl Discount for TenPercentDiscount {
fn get_discount(&self) -> f32 {
0.1
}
}
struct Apple;
struct Orange;
impl Fruit for Apple {
fn name(&self) -> &str {
"Apple"
}
fn price(&self) -> f32 {
2.0
}
}
impl Fruit for Orange {
fn name(&self) -> &str {
"Orange"
}
fn price(&self) -> f32 {
1.7
}
}
let mut container = Container::new();
add_singleton_trait!(container, Discount => TenPercentDiscount).unwrap();
add_scoped_trait!(container, "apple", Fruit => Apple).unwrap();
add_scoped_trait!(container, "orange", Fruit => Orange).unwrap();
// All types are returned as `Box<dyn Trait>`
let discount = get_resolved_trait!(container, Discount).unwrap();
let apple = get_resolved_trait!(container, Fruit, "apple").unwrap();
let orange = get_resolved_trait!(container, Fruit, "orange").unwrap();
assert_eq!(discount.get_discount(), 0.1);
assert_eq!(apple.name(), "Apple");
assert_eq!(apple.price(), 2.0);
assert_eq!(orange.name(), "Orange");
assert_eq!(orange.price(), 1.7);
```
### get, get_scoped and get_singleton
There are 3 ways to retrieve a value from the container:
- `get`
- `get_scoped`
- `get_singleton`
And it's named variants:
- `get_with_name`
- `get_scoped_with_name`
- `get_singleton_with_name`
`get_scoped` and `get_singleton` are self-explanatory, they get
a value from a `scoped` or `singleton` provider.
But `get` can get any `scoped` and `singleton` value,
the difference is that `get` returns a `Resolved<T>`
and the others returns a `T` (scoped) or `Arc<T>` (singletons).
`Resolved<T>` is just an enum for a `Scoped(T)` and `Singleton(Arc<T>)`
where you can convert it back using `into_scoped` or `into_singleton`,
it also implements `Deref` over `T`.
## Derive Inject
> This requires the `derive` feature.
Inject is implemented for all types that implement `Default`
and can be auto-implemented using `#[derive]`. When using the `derive`
types `Arc<T>` and `Singleton<T>` will be injected as singleton,
and other types as scoped unless specified.
```rust
use dilib::{Singleton, Inject, Container};
use dilib_derive::*;
#[derive(Inject)]
struct Apple {
// Singleton is an alias for Arc<T>
#[inject(name="apple")]
tag: Singleton<String>,
#[inject(name="apple_price")]
price: f32
}
let mut container = Container::new();
container.add_singleton_with_name("apple", String::from("FRUIT_APPLE")).unwrap();
let apple = container.get::<Apple>().unwrap();
assert_eq!(apple.tag.as_ref(), "FRUIT_APPLE");
assert_eq!(apple.price, 2.0);
```
## Global Container
> This requires the `global` feature.
`dilib` also offers a global container so you don't require
to declare your own, you can access the values of the container
using `get_scoped!`, `get_singleton!`or `get_resolved!`,
you can also access the container directly using `get_container()`.
```rust
use dilib::{global::init_container, resolve};
init_container(|container| {
container.add_scoped(|| String::from("Orange")).unwrap();
container.add_singleton_with_name("num", 123_i32).unwrap();
}).expect("unable to initialize the container");
let orange = resolve!(String).unwrap();
let num = resolve!(i32, "num").unwrap();
assert_eq!(orange.as_ref(), "Orange");
assert_eq!(*num, 123);
```
## Provide
> This requires the `unstable_provide` feature.
### Why unstable_provide?
The feature `unstable_provide` make possible to have dependency
injection more similar to other frameworks like C# `EF Core` or Java `Spring`.
To allow run code before main we use the the [ctor](https://github.com/mmastrac/rust-ctor) crate,
which have been tested in several OS so is stable for most of the use cases.
### provide macro
You can use the `#[provide]` macro over any function or type that implements
**`Inject` to register it to the global container.
<strong>
<small>** Any type that implements `Default` also implements `Inject`.</small>
</strong>
```rust
use std::sync::RwLock;
use dilib::global::init_container;
use dilib::{resolve, Singleton, Inject, provide};
#[allow(dead_code)]
#[derive(Debug, Clone)]
struct User {
name: &'static str,
email: &'static str,
}
trait Repository<T> {
fn add(&self, item: T);
fn get_all(&self) -> Vec<T>;
}
#[derive(Default)]
#[provide(scope="singleton")]
struct Db(RwLock<Vec<User>>);
#[derive(Inject)]
#[provide(bind="Repository<User>")]
struct UserRepository(Singleton<Db>);
impl Repository<User> for UserRepository {
fn add(&self, item: User) {
self.0.0.write().unwrap().push(item);
}
fn get_all(&self) -> Vec<User> {
self.0.0.read().unwrap().clone()
}
}
// Initialize the container to register the providers
user_repository.add(User { name: "Marie", email: "marie@example.com" });
user_repository.add(User { name: "Natasha", email: "natasha@example.com" });
let users = user_repository.get_all();
let db = resolve!(Db).unwrap();
println!("Total users: {}", db.0.read().unwrap().len());
println!("{:#?}", users);
```