An open API service indexing awesome lists of open source software.

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

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/actions

A 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 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::().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);
```