mirror of
https://github.com/TECHNOFAB11/aerosol.git
synced 2025-12-11 23:50:07 +01:00
Add documentation
This commit is contained in:
parent
d3ff99023d
commit
f47d3a417d
7 changed files with 260 additions and 4 deletions
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "aerosol"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
authors = ["Diggory Blake <diggsey@googlemail.com>"]
|
||||
edition = "2018"
|
||||
description = "Dependency injection with compile-time guarantees"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! private_define_context {
|
||||
{
|
||||
|
|
@ -144,6 +145,98 @@ macro_rules! private_define_context {
|
|||
};
|
||||
}
|
||||
|
||||
/// Define a new context. Typically used at the top level of an
|
||||
/// application to contain the full set of requried dependencies.
|
||||
///
|
||||
/// Contexts follow a struct-like syntax, although the names of
|
||||
/// fields are for the most part unimportant.
|
||||
///
|
||||
/// Contexts automatically implement all applicable interfaces.
|
||||
/// An interface is applicable if all of the dependencies
|
||||
/// required by that interface are present in the context.
|
||||
///
|
||||
/// Dependencies are identified by *type*, not by the field name.
|
||||
/// Contexts may not contain two fields of the same type. Instead
|
||||
/// use new-type wrappers to distinguish similar dependencies.
|
||||
///
|
||||
/// Types used in a context must implement `Clone + Debug`, and
|
||||
/// `Clone` should be a cheap operation. For this reason it is usual
|
||||
/// to wrap dependencies in an `Rc` or `Arc`.
|
||||
///
|
||||
/// A constructor function will be automatically implemented
|
||||
/// for contexts, with one parameter for each dependency, to be
|
||||
/// provided in the same order as when the context is defined.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// struct Foo;
|
||||
/// #[derive(Debug)]
|
||||
/// struct Bar;
|
||||
///
|
||||
/// aerosol::define_context!(
|
||||
/// TestContext {
|
||||
/// foo: Arc<Foo>,
|
||||
/// bar: Arc<Bar>,
|
||||
/// }
|
||||
/// );
|
||||
///
|
||||
/// fn main() {
|
||||
/// TestContext::new(
|
||||
/// Arc::new(Foo),
|
||||
/// Arc::new(Bar),
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// It is also possible to define a factory type to enable
|
||||
/// dependencies to be automatically created.
|
||||
///
|
||||
/// When a factory is specified for a dependency, it will be
|
||||
/// omitted from the parameter list required by the context's
|
||||
/// constructor. Instead, the constructor will call the `build`
|
||||
/// method on the specified factory.
|
||||
///
|
||||
/// To conditionally use a factory, or use different factories
|
||||
/// for the same dependency, define separate contexts, or
|
||||
/// call the factory manually and pass the result to the
|
||||
/// context's constructor in the normal way.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// use std::sync::Arc;
|
||||
/// use failure;
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// struct Foo;
|
||||
/// #[derive(Debug)]
|
||||
/// struct Bar;
|
||||
///
|
||||
/// struct FooFactory;
|
||||
/// impl aerosol::Factory for FooFactory {
|
||||
/// type Object = Arc<Foo>;
|
||||
/// fn build() -> Result<Arc<Foo>, failure::Error> { Ok(Arc::new(Foo)) }
|
||||
/// }
|
||||
///
|
||||
/// aerosol::define_context!(
|
||||
/// TestContext {
|
||||
/// foo: Arc<Foo> [FooFactory],
|
||||
/// bar: Arc<Bar>,
|
||||
/// }
|
||||
/// );
|
||||
///
|
||||
/// fn main() {
|
||||
/// TestContext::new(
|
||||
/// Arc::new(Bar),
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! define_context {
|
||||
($($input:tt)*) => (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! generate_trait_def {
|
||||
{
|
||||
|
|
@ -20,7 +20,7 @@ macro_rules! generate_trait_def {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! generate_trait_impl {
|
||||
{
|
||||
|
|
@ -44,6 +44,7 @@ macro_rules! generate_trait_impl {
|
|||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! private_define_interface {
|
||||
{
|
||||
|
|
@ -129,6 +130,57 @@ macro_rules! private_define_interface {
|
|||
};
|
||||
}
|
||||
|
||||
/// Define a new interface. Used at any layer of your application
|
||||
/// to declare what dependencies are required by that part of the
|
||||
/// program.
|
||||
///
|
||||
/// Interfaces follow a trait-like syntax, except that they may
|
||||
/// only contain "getter" methods of a particular form. The names
|
||||
/// of these methods are for the most part unimportant, but the
|
||||
/// return types are used to identify dependencies required for
|
||||
/// a context to implement this interface.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// struct Foo;
|
||||
///
|
||||
/// aerosol::define_interface!(
|
||||
/// TestInterface {
|
||||
/// fn foo(&self) -> Arc<Foo>;
|
||||
/// }
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// Interfaces may also specify super-traits, which can themselves
|
||||
/// be interfaces. Interfaces do not need to explicitly list
|
||||
/// dependencies if they are transitively required by one of their
|
||||
/// super-traits, but repeating a dependency will still only
|
||||
/// require it to be provided once.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// #![recursion_limit="128"]
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// struct Foo;
|
||||
///
|
||||
/// aerosol::define_interface!(
|
||||
/// FooInterface {
|
||||
/// fn foo(&self) -> Arc<Foo>;
|
||||
/// }
|
||||
/// );
|
||||
///
|
||||
/// aerosol::define_interface!(
|
||||
/// TestInterface: FooInterface + Clone {}
|
||||
/// );
|
||||
/// ```
|
||||
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! define_interface {
|
||||
($($input:tt)*) => (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! join {
|
||||
{
|
||||
|
|
|
|||
108
src/lib.rs
108
src/lib.rs
|
|
@ -1,3 +1,101 @@
|
|||
//! # 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;
|
||||
//! use failure;
|
||||
//!
|
||||
//! // 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>, failure::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],
|
||||
//! }
|
||||
//! );
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let context = AppContext::new().unwrap();
|
||||
//!
|
||||
//! run_app(context, 4);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! See the individual macro documentation for more details.
|
||||
|
||||
pub extern crate tt_call;
|
||||
pub extern crate failure;
|
||||
|
||||
|
|
@ -7,15 +105,25 @@ mod interface;
|
|||
mod context;
|
||||
|
||||
|
||||
/// 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 {
|
||||
type Object;
|
||||
fn build() -> Result<Self::Object, failure::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>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ aerosol::define_interface!(
|
|||
}
|
||||
);
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct FooFactory;
|
||||
#[derive(Clone, Debug)]
|
||||
struct Foo;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! parse_bound {
|
||||
{
|
||||
|
|
@ -51,6 +52,7 @@ macro_rules! parse_bound {
|
|||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! parse_trait_def {
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue