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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
//! Coi provides an easy to use dependency injection framework.
//! Currently, this crate provides the following:
//! - **[`coi::Inject` (trait)]** - a marker trait that indicates a trait is injectable, and that a struct can
//! be used to implement those injectable traits.
//! - **[`coi::Provide`]** - a trait that indicates a struct is capable of providing a specific
//! implementation of some injectable trait. This is generated for you if you use the
//! [`coi::Inject` (derive)] derive, but can also be written manually.
//! - **[`coi::Container`]** - a container to manage the lifetime of all dependencies. This is still in its
//! early stages, and currently only supports objects that are recreated with each request to
//! resolve.
//! - **[`coi::ContainerBuilder`]** - a builder for the above container to simplify construction and
//! guarantee immutability after construction.
//!
//! [`coi::Inject` (trait)]: trait.Inject.html
//! [`coi::Inject` (derive)]: derive.Inject.html
//! [`coi::Provide`]: trait.Provide.html
//! [`coi::Container`]: struct.Container.html
//! [`coi::ContainerBuilder`]: struct.ContainerBuilder.html
//!
//! # Example
//!
//! ```rust
//! use async_std;
//! use coi::{ContainerBuilder, Inject};
//! use std::sync::Arc;
//!
//! // Mark injectable traits by inheriting the `Inject` trait.
//! trait Trait1: Inject {
//!     fn describe(&self) -> &'static str;
//! }
//!
//! // For structs that will provide the implementation of an injectable trait, derive `Inject`
//! // and specify which method will be used to inject which trait. The method can be any path.
//! // The arguments for the method are derived from fields marked with the attribute `#[inject]`
//! // (See Impl2 below).
//! #[derive(Inject)]
//! // Currently, only one trait can be provided, but this will likely be expanded on in future
//! // versions of this crate.
//! #[provides(dyn Trait1 with Impl1)]
//! struct Impl1;
//!
//! // Don't forget to actually implement the trait ;).
//! impl Trait1 for Impl1 {
//!     fn describe(&self) -> &'static str {
//!         "I'm impl1!"
//!     }
//! }
//!
//! // Mark injectable traits by inheriting the `Inject` trait.
//! trait Trait2: Inject {
//!     fn deep_describe(&self) -> String;
//! }
//!
//! // For structs that will provide the implementation of an injectable trait, derive `Inject`
//! // and specify which method will be used to inject which trait. The arguments for the method
//! // are derived from fields marked with the attribute `#[inject]` in the order they appear.
//! // Future versions of this crate might allow you to control the order of the args with
//! // parameters to the `#[inject]` attribute.
//! #[derive(Inject)]
//! #[provides(dyn Trait2 with Impl2::new(trait1))]
//! struct Impl2 {
//!     // The name of the field is important! It must match the name that's registered in the
//!     // container when the container is being built! This is similar to the behavior of
//!     // dependency injection libraries in other languages.
//!     #[inject]
//!     trait1: Arc<dyn Trait1>,
//! }
//!
//! // Implement the provider method
//! impl Impl2 {
//!     // Note: The param name here doesn't actually matter.
//!     fn new(trait1: Arc<dyn Trait1>) -> Self {
//!         Self { trait1 }
//!     }
//! }
//!
//! // Again, don't forget to actually implement the trait ;).
//! impl Trait2 for Impl2 {
//!     fn deep_describe(&self) -> String {
//!         format!("I'm impl2! and I have {}", self.trait1.describe())
//!     }
//! }
//!
//! // You might note that Container::resolve is async. This is to allow any provider to be async
//! // and since we don't know from the resolution perspective whether any provider will need to be
//! // async, they all have to be. This might be configurable through feature flags in a future
//! // version of the library.
//! #[async_std::main]
//! async fn main() {
//!     // "Provider" structs are automatically generated through the `Inject` attribute. They
//!     // append `Provider` to the name of the struct that is being derive (make sure you don't
//!     // any structs with the same name or your code will fail to compile.
//!     // Reminder: Make sure you use the same key here as the field names of the structs that
//!     // require these impls.
//!     let container = ContainerBuilder::new()
//!         .register("trait1", Impl1Provider)
//!         .register("trait2", Impl2Provider)
//!         .build();
//!
//!     // Once the container is built, you can now resolve any particular instance by its key and
//!     // the trait it provides. This crate currently only supports `Arc<dyn Trait>`, but this may
//!     // be expanded in a future version of the crate.
//!     let trait2 = container
//!         // Note: Getting the key wrong will produce an error telling you which key in the
//!         // chain of dependencies caused the failure (future versions might provider a vec of
//!         // chain that lead to the failure). Getting the type wrong will only tell you which key
//!         // had the wrong type. This is because at runtime, we do not have any type information,
//!         // only unique ids (that change during each compilation).
//!         .resolve::<Arc<dyn Trait2>>("trait2")
//!         .await
//!         .expect("Should exist");
//!     println!("Deep description: {}", trait2.deep_describe());
//! }
//! ```
//!

use async_trait::async_trait;
pub use coi_derive::*;
use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Display};
use std::sync::Arc;

#[derive(Debug)]
pub enum Error {
    /// This key was not found in the container. Either the requested resource was never registered
    /// with this container, or there is a typo in the register or resolve calls.
    KeyNotFound(String),
    /// The requested key was found in the container, but its type did not match the requested type.
    TypeMismatch(String),
    /// Wrapper around errors produced by `Provider`s.
    Inner(Box<dyn std::error::Error + Send + Sync + 'static>),
}

impl Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> {
        match self {
            Error::KeyNotFound(s) => write!(f, "Key not found: {}", s),
            Error::TypeMismatch(s) => write!(f, "Type mismatch for key: {}", s),
            Error::Inner(ptr) => write!(f, "Inner error: {}", ptr.description()),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Error::KeyNotFound(_) | Error::TypeMismatch(_) => None,
            Error::Inner(ptr) => Some(ptr.as_ref()),
        }
    }
}

pub type Result<T> = std::result::Result<T, Error>;

pub trait Inject: Send + Sync + 'static {}

impl<T: Inject + ?Sized> Inject for Arc<T> {}

#[derive(Clone)]
pub struct Container {
    provider_map: HashMap<String, Arc<dyn Any + Send + Sync + 'static>>,
}

impl Container {
    /// Construct or lookup a previously constructed object of type `T` with key `key`.
    pub async fn resolve<T>(&self, key: &str) -> Result<T>
    where
        T: Inject,
    {
        let any_provider = self
            .provider_map
            .get(key)
            .ok_or_else(|| Error::KeyNotFound(key.to_owned()))?;
        let provider = any_provider
            .downcast_ref::<Arc<dyn Provide<Output = T> + Send + Sync + 'static>>()
            .ok_or_else(|| Error::TypeMismatch(key.to_owned()))?;
        // FIXME(pfaria) memoize results when singleton registration is introduced
        provider.provide(self).await
    }
}

/// A builder used to construct a `Container`
#[derive(Clone)]
pub struct ContainerBuilder {
    provider_map: HashMap<String, Arc<dyn Any + Send + Sync + 'static>>,
}

impl ContainerBuilder {
    pub fn new() -> Self {
        Self {
            provider_map: HashMap::new(),
        }
    }

    /// Register a `Provider` for `T`.
    pub fn register<K, P, T>(mut self, key: K, provider: P) -> Self
    where
        K: Into<String>,
        T: Inject,
        P: Provide<Output = T> + Send + Sync + 'static,
    {
        let key = key.into();
        let provider = Arc::new(provider) as Arc<dyn Provide<Output = T> + Send + Sync + 'static>;
        self.provider_map.insert(key, Arc::new(provider));
        self
    }

    /// Consumer this builder to produce a `Container`.
    pub fn build(self) -> Container {
        Container {
            provider_map: self.provider_map,
        }
    }
}

#[async_trait]
pub trait Provide {
    /// The type that this provider is intended to produce
    type Output: Inject;

    /// Only intended to be used internally
    async fn provide(&self, container: &Container) -> Result<Self::Output>;
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn ensure_display() {
        use std::io;

        let error = Error::KeyNotFound("S".to_owned());
        let displayed = format!("{}", error);
        assert_eq!(displayed, "Key not found: S");

        let error = Error::TypeMismatch("S2".to_owned());
        let displayed = format!("{}", error);
        assert_eq!(displayed, "Type mismatch for key: S2");

        let error = Error::Inner(Box::new(io::Error::new(io::ErrorKind::NotFound, "oh no!")));
        let displayed = format!("{}", error);
        assert_eq!(displayed, "Inner error: oh no!");
    }

    #[test]
    fn ensure_debug() {
        let error = Error::KeyNotFound("S".to_owned());
        let debugged = format!("{:?}", error);
        assert_eq!(debugged, "KeyNotFound(\"S\")");

        let error = Error::TypeMismatch("S2".to_owned());
        let debugged = format!("{:?}", error);
        assert_eq!(debugged, "TypeMismatch(\"S2\")");
    }

    #[test]
    fn conainer_builder_is_clonable() {
        let builder = ContainerBuilder::new();
        for _ in 0..2 {
            let builder = builder.clone();
            let _container = builder.build();
        }
    }

    #[test]
    fn container_is_clonable() {
        let container = ContainerBuilder::new().build();
        let _container = container.clone();
    }
}