coi_actix_web/
lib.rs

1#![deny(missing_docs)]
2
3//! This crate provides a simple Dependency Injection framework for `actix-web`.
4//!
5//! ## Example
6//!
7//! Note that the following example is heavily minified. Files names don't really matter. For a
8//! more involved example, please see the [`coi-actix-sample`] repository.
9//!
10//! [`coi-actix-sample`]: https://github.com/Nashenas88/coi-actix-sample
11//!
12//! In your main binary:
13//! ```rust,ignore
14//! use crate::infrastructure::{RepositoryProvider, PoolProvider};
15//! use crate::service::ServiceProvider;
16//! use coi::container;
17//! use actix_web::{App, HttpServer};
18//!
19//! mod traits;
20//! mod infrastructure;
21//! mod routes;
22//! mod service;
23//!
24//! # let _s = "
25//! fn main() {
26//! # ";
27//!     // container! only expects identifiers, so construct this provider outside
28//!     let postgres_pool = PoolProvider::<NoTls>::new(/* construct actual pool */);
29//!
30//!     // Build your container
31//!     let container = container! {
32//!         pool => postgres_pool.singleton,
33//!         service => ServiceProvider.scoped,
34//!         repository => RepositoryProvider.scoped,
35//!     };
36//!
37//!     HttpServer::new(move || {
38//!         App::new()
39//!              // Make sure to assign it to `app_data` and not `data`
40//!             .app_data(container.clone())
41//!             .configure(routes::data::route_config)
42//!     })
43//!     ...
44//! # let _ = "
45//! }
46//! # ";
47//! ```
48//!
49//! `traits.rs`:
50//! ```rust,ignore
51//! use coi::Inject;
52//!
53//! // Ensure all of your traits inherit from `Inject`
54//! pub trait IService: Inject {
55//!     ...
56//! }
57//!
58//! pub trait IRepository: Inject {
59//!     ...
60//! }
61//! ```
62//!
63//! `service.rs`
64//! ```rust,ignore
65//! use crate::traits::IService;
66//! use coi::Inject;
67//! use std::sync::Arc;
68//!
69//! // derive `Inject` for all structs that will provide the injectable traits.
70//! #[derive(Inject)]
71//! #[coi(provides pub dyn IService with Service::new(repository))]
72//! struct Service {
73//!     #[coi(inject)]
74//!     repository: Arc<dyn IRepository>,
75//! }
76//!
77//! impl IService for Service {
78//!     ...
79//! }
80//! ```
81//!
82//! > **Note**: See [`coi::Inject`] for more examples on how to use `#[derive(Inject)]`
83//!
84//! [`coi::Inject`]: derive.Inject.html
85//!
86//! `infrastructure.rs`
87//! ```rust,ignore
88//! use crate::traits::IRepository;
89//! use coi::Inject;
90//! use ...::PostgresPool;
91//! #[cfg(feature = "notls")]
92//! use ...::NoTls;
93//! #[cfg(not(feature = "notls"))]
94//! use ...::Tls;
95//!
96//! #[derive(Inject)]
97//! #[coi(provides pub dyn IRepository with Repository::new(pool))]
98//! struct Repository {
99//!     #[cfg(feature = "notls")]
100//!     #[coi(inject)]
101//!     pool: PostgresPool<NoTls>,
102//!
103//!     #[cfg(not(feature = "notls"))]
104//!     #[coi(inject)]
105//!     pool: PostgresPool<Tls>,
106//! }
107//!
108//! impl IRepository for Repository {
109//!     ...
110//! }
111//!
112//! #[derive(Inject)]
113//! struct Pool<T> where T: ... {
114//!     pool: PostgresPool<T>
115//! }
116//!
117//! #[derive(Provide)]
118//! #[coi(provides pub Pool<T> with Pool::new(self.0.pool))]
119//! struct PoolProvider<T> where T: ... {
120//!     pool: PostgresPool<T>
121//! }
122//!
123//! impl<T> PoolProvider<T> where T: ... {
124//!     fn new(PostgresPool<T>) -> Self { ... }
125//! }
126//! ```
127//!
128//! `routes.rs`
129//! ```rust,ignore
130//! use crate::service::IService;
131//! use actix_web::{Error, HttpResponse, Responder};
132//! use actix_web::web::{self, ServiceConfig};
133//! use coi_actix_web::inject;
134//! use std::sync::Arc;
135//!
136//! #[inject(coi_crate = "coi")]
137//! async fn get(
138//!     id: web::Path<i64>,
139//!     #[inject] service: Arc<dyn IService>,
140//! ) -> Result<impl Responder, Error> {
141//!     let name: String = service.get(*id).await?;
142//!     Ok(HttpResponse::Ok().json(name))
143//! }
144//!
145//! #[inject(coi_crate = "coi")]
146//! async fn get_all(#[inject] service: Arc<dyn IService>) -> Result<impl Responder, Error> {
147//!     let data: Vec<String> = service.get_all().await?;
148//!     Ok(HttpResponse::Ok().json(data))
149//! }
150//!
151//! pub fn route_config(config: &mut ServiceConfig) {
152//!     config.service(
153//!         web::scope("/data")
154//!             .route("", web::get().to(get_all))
155//!             .route("/", web::get().to(get_all))
156//!             .route("/{id}", web::get().to(get)),
157//!     );
158//! }
159//! ```
160
161use actix_service::ServiceFactory;
162use actix_web::dev::ServiceRequest;
163use actix_web::Error;
164
165/// Extensions to `actix-web`'s `App` struct
166pub trait AppExt {
167    /// A helper extension method to ensure the `Container` is
168    /// properly registered to work with the `inject` attribute macro.
169    ///
170    /// # Examples
171    ///
172    /// ```no_run
173    /// use coi_actix_web::AppExt as _;
174    ///
175    /// // Your general server setup in "main". The name here is different
176    /// #[actix_rt::main]
177    /// async fn main() -> std::io::Result<()> {
178    ///     use actix_web::{App, HttpServer};
179    ///     use coi::container;
180    ///
181    ///     // Construct your coi container with your keys and providers
182    ///     // See the coi crate for more details
183    ///     let container = container!{
184    ///         service => ServiceImplProvider; scoped
185    ///     };
186    ///
187    ///     HttpServer::new(move || {
188    ///         App::new()
189    ///         .register_container(container.clone())
190    ///         // ^^^^^^^^^^^^^^^^
191    ///         .service(get)
192    ///     })
193    ///     .bind("127.0.0.1:8000")?
194    ///     .run()
195    ///     .await
196    /// }
197    ///
198    /// # use coi::{Container, Inject, Provide};
199    /// # use std::sync::Arc;
200    /// #
201    /// # struct ServiceImpl;
202    /// #
203    /// # impl Inject for ServiceImpl {}
204    /// #
205    /// # struct ServiceImplProvider;
206    /// #
207    /// # impl Provide for ServiceImplProvider {
208    /// #
209    /// #     type Output = ServiceImpl;
210    /// #
211    /// #     fn provide(&self, _: &Container) -> coi::Result<Arc<Self::Output>> {
212    /// #         Ok(Arc::new(ServiceImpl))
213    /// #     }
214    /// # }
215    /// #
216    /// # use actix_web::{get, web, Error, HttpResponse, Responder};
217    /// #
218    /// # // Add the `inject` attribute to the function you want to inject
219    /// # #[get("/{id}")]
220    /// # #[coi_actix_web::inject]
221    /// # async fn get(
222    /// #     id: web::Path<u64>,
223    /// #     // Add the `inject` field attribute to each attribute you want
224    /// #     // injected
225    /// #     #[inject] service: Arc<ServiceImpl>
226    /// # ) -> Result<impl Responder, Error> {
227    /// #     let _ = service;
228    /// #     Ok(HttpResponse::Ok())
229    /// # }
230    ///  
231    /// ```
232    fn register_container(self, container: Container) -> Self;
233}
234
235impl<T> AppExt for actix_web::App<T>
236where
237    T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
238{
239    fn register_container(self, container: Container) -> Self {
240        self.app_data(container)
241    }
242}
243
244use coi::{Container, Inject};
245pub use coi_actix_web_derive::*;
246
247use actix_web::dev::Payload;
248use actix_web::error::ErrorInternalServerError;
249use actix_web::{Error as WebError, FromRequest, HttpRequest};
250use futures::future::{err, ok, ready, Ready};
251use std::marker::PhantomData;
252use std::sync::Arc;
253
254#[doc(hidden)]
255pub trait ContainerKey<T>
256where
257    T: Inject + ?Sized,
258{
259    const KEY: &'static str;
260}
261
262#[doc(hidden)]
263pub struct Injected<T, K>(pub T, pub PhantomData<K>);
264
265impl<T, K> Injected<T, K> {
266    #[doc(hidden)]
267    pub fn new(injected: T) -> Self {
268        Self(injected, PhantomData)
269    }
270}
271
272impl<T, K> FromRequest for Injected<Arc<T>, K>
273where
274    T: Inject + ?Sized,
275    K: ContainerKey<T>,
276{
277    type Error = WebError;
278    type Future = Ready<Result<Self, Self::Error>>;
279
280    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
281        match req.app_data::<Container>() {
282            Some(container) => {
283                let container = container.scoped();
284                ready(
285                    container
286                        .resolve::<T>(K::KEY)
287                        .map(Injected::new)
288                        .map_err(ErrorInternalServerError),
289                )
290            }
291            None => err(ErrorInternalServerError("Container not registered")),
292        }
293    }
294}
295
296macro_rules! injected_tuples {
297    ($(($T:ident, $K:ident)),+) => {
298        impl<$($T, $K),+> FromRequest for Injected<($(Arc<$T>),+), ($($K),+)>
299        where $(
300            $T: Inject + ?Sized + 'static,
301            $K: ContainerKey<$T>,
302        )+
303        {
304            type Error = WebError;
305            type Future = Ready<Result<Self, Self::Error>>;
306
307            fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
308                match req.app_data::<Container>() {
309                    Some(container) => {
310                        let container = container.scoped();
311                        ok(Injected::new(($(
312                            {
313                                let resolved = container.resolve::<$T>(<$K as ContainerKey<$T>>::KEY)
314                                    .map_err(ErrorInternalServerError);
315                                match resolved {
316                                    Ok(r) => r,
317                                    Err(e) => return err(e),
318                                }
319                            },
320                        )+)))
321                    },
322                    None => err(ErrorInternalServerError("Container not registered"))
323                }
324            }
325        }
326    }
327}
328
329injected_tuples!((TA, KA), (TB, KB));
330injected_tuples!((TA, KA), (TB, KB), (TC, KC));
331injected_tuples!((TA, KA), (TB, KB), (TC, KC), (TD, KD));
332injected_tuples!((TA, KA), (TB, KB), (TC, KC), (TD, KD), (TE, KE));
333injected_tuples!((TA, KA), (TB, KB), (TC, KC), (TD, KD), (TE, KE), (TF, KF));
334injected_tuples!(
335    (TA, KA),
336    (TB, KB),
337    (TC, KC),
338    (TD, KD),
339    (TE, KE),
340    (TF, KF),
341    (TG, KG)
342);
343injected_tuples!(
344    (TA, KA),
345    (TB, KB),
346    (TC, KC),
347    (TD, KD),
348    (TE, KE),
349    (TF, KF),
350    (TG, KG),
351    (TH, KH)
352);
353injected_tuples!(
354    (TA, KA),
355    (TB, KB),
356    (TC, KC),
357    (TD, KD),
358    (TE, KE),
359    (TF, KF),
360    (TG, KG),
361    (TH, KH),
362    (TI, KI)
363);
364injected_tuples!(
365    (TA, KA),
366    (TB, KB),
367    (TC, KC),
368    (TD, KD),
369    (TE, KE),
370    (TF, KF),
371    (TG, KG),
372    (TH, KH),
373    (TI, KI),
374    (TJ, KJ)
375);
376injected_tuples!(
377    (TA, KA),
378    (TB, KB),
379    (TC, KC),
380    (TD, KD),
381    (TE, KE),
382    (TF, KF),
383    (TG, KG),
384    (TH, KH),
385    (TI, KI),
386    (TJ, KJ),
387    (TK, KK)
388);