1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//! # aerosol
//! Simple dependency injection for Rust
//!
//! The two main exports of this crate are the `define_context`
//! and `define_interface` macros.
//!
//! Contexts are containers for multiple dependencies, allowing
//! them to be passed around as one with relative ease. Interfaces
//! are specialized traits which place constraints on contexts,
//! indicating exactly what dependencies a context must provide.
//!
//! Contexts are typically created at the top level of an application,
//! as they specify exactly what concrete versions of all dependencies
//! are going to be used. A single context is created with a precise
//! set of depenencies, and is then threaded through the rest of the
//! application as a generic parameter.
//!
//! Interfaces are used at every level of an application, as they
//! allow each piece of code to independently specify what dependencies
//! are required. Interfaces can "inherit" the dependencies of other
//! interfaces, with the idea being that this inheritance will form
//! a tree, such that there will be some "root interface" which contains
//! the union of all dependencies required by the whole application.
//!
//! This pattern allows dependencies to be added or removed from any
//! part of the application without having to modify the code at every
//! level, to thread or un-thread the new or old dependencies through.
//!
//! ## Example
//!
//! ```
//! #![recursion_limit="128"]
//! use std::sync::Arc;
//! use std::fmt::Debug;
//!
//! // We will depend on some kind of logger
//! trait Logger: Debug {
//!     fn log(&self, msg: &str);
//! }
//!
//! // We have a specific implementation of a stdout logger
//! #[derive(Debug)]
//! struct StdoutLogger;
//!
//! impl Logger for StdoutLogger {
//!     fn log(&self, msg: &str) {
//!         println!("{}", msg);
//!     }
//! }
//!
//! struct StdoutLoggerFactory;
//! impl aerosol::Factory for StdoutLoggerFactory {
//!     type Object = Arc<Logger>;
//!     fn build(_: ()) -> Result<Arc<Logger>, anyhow::Error> {
//!         Ok(Arc::new(StdoutLogger))
//!     }
//! }
//!
//! // Part of our application does some work
//! aerosol::define_interface!(
//!     WorkerInterface {
//!         fn logger(&self) -> Arc<Logger>;
//!     }
//! );
//!
//! fn do_work<I: WorkerInterface>(iface: I) {
//!     iface.logger().log("Doing some work!");
//! }
//!
//! // Our application does multiple pieces of work
//! aerosol::define_interface!(
//!     AppInterface: WorkerInterface + Clone {}
//! );
//!
//! fn run_app<I: AppInterface>(iface: I, num_work_items: usize) {
//!     for _ in 0..num_work_items {
//!         do_work(iface.clone());
//!     }
//! }
//!
//! // At the very top level, we specify the implementations
//! // of our dependencies.
//! aerosol::define_context!(
//!     AppContext {
//!         logger: Arc<Logger> [StdoutLoggerFactory],
//!     }
//! );
//!
//! let context = AppContext::new().unwrap();
//!
//! run_app(context, 4);
//! ```
//!
//! See the individual macro documentation for more details.

#[doc(hidden)]
pub extern crate tt_call;

mod context;
mod interface;
mod join;
mod parse;

/// The building block for this crate. Automatically implemented
/// for contexts providing a dependency of type `T`.
///
/// Super-trait of all interfaces requiring a dependency of type
/// `T`.
pub trait Provide<T> {
    fn provide(&self) -> T;
}

/// Implement this trait to provide a convenient syntax for
/// constructing implementations of dependencies.
pub trait Factory<Args = ()> {
    type Object;
    fn build(args: Args) -> Result<Self::Object, anyhow::Error>;
}

/// Allows cloning a context whilst replacing one dependency
/// with a different implementation. Must be explicitly listed
/// as a super-trait of an interface to use.
pub trait ProvideWith<T>: Provide<T> + Sized {
    fn provide_with<E, F: FnOnce(T) -> Result<T, E>>(&self, f: F) -> Result<Self, E>;
}