https://github.com/neo-ciber94/dilib-rs
A dependency injection library for Rust
https://github.com/neo-ciber94/dilib-rs
dependency-injection rust
Last synced: 7 months ago
JSON representation
A dependency injection library for Rust
- Host: GitHub
- URL: https://github.com/neo-ciber94/dilib-rs
- Owner: Neo-Ciber94
- License: mit
- Created: 2021-07-13T02:44:39.000Z (about 4 years ago)
- Default Branch: master
- Last Pushed: 2022-12-19T22:11:12.000Z (almost 3 years ago)
- Last Synced: 2024-10-12T07:15:36.903Z (12 months ago)
- Topics: dependency-injection, rust
- Language: Rust
- Homepage:
- Size: 391 KB
- Stars: 53
- Watchers: 2
- Forks: 5
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# 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/actionsA dependency injection library for Rust.
## Usage
```toml
[dependencies]
dilib = "0.2.1"
```## 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::().unwrap();
let en = container.get::().unwrap();
let es = container.get_with_name::("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 timeAll 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::().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::>().unwrap();
*c1.lock().unwrap() = 3;
}let c2 = container.get::>().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::().unwrap();
let id = generator.next();
let tag = container.get_with_name::("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::().unwrap();let f1 = container.get::().unwrap();
let f2 = container.get::().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`.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`.```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`
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`
and the others returns a `T` (scoped) or `Arc` (singletons).`Resolved` is just an enum for a `Scoped(T)` and `Singleton(Arc)`
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` and `Singleton` 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
#[inject(name="apple")]
tag: Singleton,
#[inject(name="apple_price")]
price: f32
}let mut container = Container::new();
container.add_singleton_with_name("apple", String::from("FRUIT_APPLE")).unwrap();
container.add_scoped_with_name("apple_price", || 2.0_f32).unwrap();
container.add_deps::();let apple = container.get::().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.
** Any type that implements `Default` also implements `Inject`.```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 {
fn add(&self, item: T);
fn get_all(&self) -> Vec;
}#[derive(Default)]
#[provide(scope="singleton")]
struct Db(RwLock>);#[derive(Inject)]
#[provide(bind="Repository")]
struct UserRepository(Singleton);
impl Repository for UserRepository {
fn add(&self, item: User) {
self.0.0.write().unwrap().push(item);
}fn get_all(&self) -> Vec {
self.0.0.read().unwrap().clone()
}
}// Initialize the container to register the providers
init_container(|_container| {
// Add additional providers
}).unwrap();let user_repository = resolve!(trait Repository).unwrap();
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);
```