injectium_salvo/lib.rs
1//! Salvo integration for Injectium dependency injection.
2//!
3//! This crate provides seamless integration between the [Injectium](https://crates.io/crates/injectium)
4//! dependency injection container and the [Salvo](https://salvo.rs) web framework.
5//!
6//! # Quick Start
7//!
8//! ## 1. Define your services with `#[derive(Injectable)]`
9//!
10//! ```ignore
11//! use injectium::Injectable;
12//!
13//! #[derive(Clone)]
14//! struct Database {
15//! connection_string: String,
16//! }
17//!
18//! #[derive(Clone, Injectable)]
19//! struct UserService {
20//! db: Database,
21//! }
22//! ```
23//!
24//! ## 2. Build the container
25//!
26//! ```ignore
27//! use injectium::{cloned, container};
28//! use std::sync::Arc;
29//!
30//! let container = Arc::new(container! {
31//! providers: [
32//! cloned(Database { connection_string: "postgres://localhost".into() }),
33//! ],
34//! });
35//! ```
36//!
37//! ## 3. Configure Salvo router
38//!
39//! ```ignore
40//! use salvo::prelude::*;
41//! use injectium_salvo::{inject_container, Injected};
42//!
43//! #[handler]
44//! async fn get_users(users: Injected<UserService>) -> String {
45//! format!("Users DB: {}", users.db.connection_string)
46//! }
47//!
48//! let router = Router::new()
49//! .hoop(inject_container(container))
50//! .push(Router::with_path("/users").get(get_users));
51//! ```
52//!
53//! # Key Types
54//!
55//! - [`Injected<T>`] - Automatically resolves `T` from the container in your
56//! handlers
57//! - [`inject_container`] - Registers the container into Salvo's request scope
58//!
59//! # Features
60//!
61//! - `oapi` - Enable OpenAPI support for `Injected<T>` types
62//!
63//! # Error Handling
64//!
65//! If the container is not registered or a dependency is missing, the handler
66//! will panic with a helpful message. Use [`Container::validate`] at startup to
67//! catch missing dependencies early.
68//!
69//! # Thread Safety
70//!
71//! All injected types must implement `Send + Sync + 'static` to be safely
72//! shared across async tasks.
73//!
74//! # Example
75//!
76//! See the [injectium examples](https://github.com/so1ve/injectium/tree/main/examples) for a complete working example.
77
78use std::ops::Deref;
79use std::sync::Arc;
80
81use cfg_block::cfg_block;
82use injectium::{Container, Injectable};
83use salvo::extract::{Extractible, Metadata};
84use salvo::http::{ParseError, Request};
85use salvo::prelude::*;
86
87/// A wrapper type for injecting dependencies from an Injectium container into
88/// Salvo handlers.
89///
90/// `Injected<T>` automatically resolves `T` from the [`Container`] stored in
91/// the Salvo [`Depot`]. The type `T` must implement [`injectium::Injectable`]
92/// and `Send + Sync + 'static`.
93///
94/// # Example
95///
96/// ```ignore
97/// use injectium::{Injectable, cloned, container};
98/// use injectium_salvo::{Injected, inject_container};
99/// use salvo::prelude::*;
100/// use std::sync::Arc;
101///
102/// #[derive(Clone, Injectable)]
103/// struct DbService {
104/// conn: String,
105/// }
106///
107/// #[handler]
108/// async fn hello(user: Injected<DbService>) -> String {
109/// format!("DB: {}", user.conn)
110/// }
111///
112/// let container = Arc::new(container! {
113/// providers: [cloned(DbService { conn: "localhost".into() })],
114/// });
115///
116/// let router = Router::new()
117/// .hoop(inject_container(container))
118/// .push(Router::with_path("/hello").get(hello));
119/// ```
120pub struct Injected<T>(pub T);
121
122impl<T> Deref for Injected<T> {
123 type Target = T;
124
125 fn deref(&self) -> &T {
126 &self.0
127 }
128}
129
130impl<'ex, T> Extractible<'ex> for Injected<T>
131where
132 T: Injectable + Send + Sync + 'static,
133{
134 fn metadata() -> &'static Metadata {
135 static METADATA: Metadata = Metadata::new("");
136
137 &METADATA
138 }
139
140 #[allow(refining_impl_trait)]
141 async fn extract(_req: &'ex mut Request, depot: &'ex mut Depot) -> Result<Self, ParseError> {
142 let container = depot
143 .obtain::<Arc<Container>>()
144 .map_err(|_| ParseError::other("container not found in depot"))?;
145 let value = T::from_container(container);
146
147 Ok(Self(value))
148 }
149}
150
151cfg_block! {
152 #[cfg(feature = "oapi")] {
153 use salvo::oapi::{Components, EndpointArgRegister, Operation};
154
155 impl<T> EndpointArgRegister for Injected<T>
156 where
157 T: Injectable + Send + Sync + 'static,
158 {
159 fn register(_components: &mut Components, _operation: &mut Operation, _arg: &str) {}
160 }
161 }
162}
163
164/// Register a [`Container`] into the Salvo [`Depot`] so that [`Injected<T>`]
165/// can resolve dependencies from it.
166///
167/// Call this in your router setup, e.g.:
168///
169/// ```ignore
170/// Router::new()
171/// .hoop(inject_container(Arc::new(container)))
172/// ...
173/// ```
174#[must_use]
175pub fn inject_container(container: Arc<Container>) -> impl Handler {
176 affix_state::inject(container)
177}