Skip to main content

reinhardt_views/viewsets/
injectable.rs

1//! Dependency injection support for ViewSets
2//!
3//! This module provides the `InjectableViewSet` extension trait that enables
4//! dependency injection in ViewSet methods.
5//!
6//! # Usage
7//!
8//! ```ignore
9//! use reinhardt_views::viewsets::{InjectableViewSet, ModelViewSet, ViewSet};
10//! use reinhardt_di::Injectable;
11//! use std::sync::Arc;
12//!
13//! impl MyViewSet {
14//!     async fn handle_list(&self, request: Request) -> Result<Response> {
15//!         // Resolve dependencies from the request's DI context
16//!         let db: Arc<DatabaseConnection> = self.resolve(&request).await?;
17//!         let cache: CacheService = self.resolve_uncached(&request).await?;
18//!
19//!         // Use the dependencies
20//!         let items = db.fetch_all().await?;
21//!         Ok(Response::ok().with_json(&items)?)
22//!     }
23//! }
24//! ```
25
26use crate::ViewSet;
27use async_trait::async_trait;
28use reinhardt_di::{Injectable, InjectionContext};
29use reinhardt_http::{Request, Result};
30use std::sync::Arc;
31
32/// Extension trait for ViewSets that enables dependency injection
33///
34/// This trait is automatically implemented for all types that implement `ViewSet`.
35/// It provides helper methods to resolve dependencies from the request's DI context.
36///
37/// # Examples
38///
39/// ```ignore
40/// # #[tokio::main]
41/// # async fn main() {
42/// use reinhardt_views::viewsets::{InjectableViewSet, ModelViewSet, ViewSet};
43/// use std::sync::Arc;
44///
45/// struct UserViewSet {
46///     basename: String,
47/// }
48///
49/// impl UserViewSet {
50///     async fn handle_list(&self, request: Request) -> Result<Response> {
51///         // Resolve with caching (default)
52///         let db: Arc<DatabaseConnection> = self.resolve(&request).await?;
53///
54///         // Resolve without caching
55///         let fresh_config: Config = self.resolve_uncached(&request).await?;
56///
57///         // Use dependencies...
58///         Ok(Response::ok())
59///     }
60/// }
61/// # }
62/// ```
63#[async_trait]
64pub trait InjectableViewSet: ViewSet {
65	/// Resolve a dependency from the request's DI context with caching
66	///
67	/// This method extracts the `InjectionContext` from the request and resolves
68	/// the requested dependency type. The resolved dependency is cached for the
69	/// duration of the request.
70	///
71	/// # Errors
72	///
73	/// Returns an error if:
74	/// - The DI context is not set on the request (router misconfiguration)
75	/// - The dependency cannot be resolved (not registered, circular dependency, etc.)
76	///
77	/// # Examples
78	///
79	/// ```ignore
80	/// let db: Arc<DatabaseConnection> = self.resolve(&request).await?;
81	/// ```
82	async fn resolve<T>(&self, request: &Request) -> Result<T>
83	where
84		T: Injectable + Clone + Send + Sync + 'static,
85	{
86		let di_ctx = request
87			.get_di_context::<Arc<InjectionContext>>()
88			.ok_or_else(|| {
89				reinhardt_core::exception::Error::Internal(
90					"DI context not set. Ensure the router is configured with .with_di_context()"
91						.to_string(),
92				)
93			})?;
94
95		match di_ctx.resolve::<T>().await {
96			Ok(injected) => Ok(injected.as_ref().clone()),
97			Err(reinhardt_di::DiError::DependencyNotRegistered { .. }) => {
98				T::inject(&di_ctx).await.map_err(|e| {
99					reinhardt_core::exception::Error::Internal(format!(
100						"Dependency injection failed for {}: {:?}",
101						std::any::type_name::<T>(),
102						e
103					))
104				})
105			}
106			Err(e) => Err(reinhardt_core::exception::Error::Internal(format!(
107				"Dependency injection failed for {}: {:?}",
108				std::any::type_name::<T>(),
109				e
110			))),
111		}
112	}
113
114	/// Resolve a dependency from the request's DI context without caching
115	///
116	/// This method is similar to `resolve()` but creates a fresh instance
117	/// of the dependency each time, bypassing the cache.
118	///
119	/// Use this when you need:
120	/// - A fresh instance that won't share state with other resolutions
121	/// - To avoid caching for dependencies with mutable state
122	///
123	/// # Errors
124	///
125	/// Returns an error if:
126	/// - The DI context is not set on the request (router misconfiguration)
127	/// - The dependency cannot be resolved (not registered, circular dependency, etc.)
128	///
129	/// # Examples
130	///
131	/// ```ignore
132	/// let fresh_service: MyService = self.resolve_uncached(&request).await?;
133	/// ```
134	async fn resolve_uncached<T>(&self, request: &Request) -> Result<T>
135	where
136		T: Injectable + Clone + Send + Sync + 'static,
137	{
138		let di_ctx = request
139			.get_di_context::<Arc<InjectionContext>>()
140			.ok_or_else(|| {
141				reinhardt_core::exception::Error::Internal(
142					"DI context not set. Ensure the router is configured with .with_di_context()"
143						.to_string(),
144				)
145			})?;
146
147		T::inject_uncached(&di_ctx).await.map_err(|e| {
148			reinhardt_core::exception::Error::Internal(format!(
149				"Dependency injection failed for {}: {:?}",
150				std::any::type_name::<T>(),
151				e
152			))
153		})
154	}
155
156	/// Try to resolve a dependency, returning None if DI context is not available
157	///
158	/// This is useful for optional dependencies or when you want to gracefully
159	/// handle the case where DI is not configured.
160	///
161	/// # Examples
162	///
163	/// ```ignore
164	/// if let Some(cache) = self.try_resolve::<CacheService>(&request).await {
165	///     // Use cache
166	/// } else {
167	///     // Fallback without cache
168	/// }
169	/// ```
170	async fn try_resolve<T>(&self, request: &Request) -> Option<T>
171	where
172		T: Injectable + Clone + Send + Sync + 'static,
173	{
174		let di_ctx = request.get_di_context::<Arc<InjectionContext>>()?;
175
176		match di_ctx.resolve::<T>().await {
177			Ok(injected) => Some(injected.as_ref().clone()),
178			Err(reinhardt_di::DiError::DependencyNotRegistered { .. }) => {
179				T::inject(&di_ctx).await.ok()
180			}
181			Err(_) => None,
182		}
183	}
184}
185
186// Blanket implementation for all ViewSet types
187impl<V: ViewSet> InjectableViewSet for V {}
188
189#[cfg(test)]
190mod tests {
191	use super::*;
192	use crate::viewsets::GenericViewSet;
193
194	// Basic compilation test - InjectableViewSet is automatically implemented
195	#[test]
196	fn test_injectable_viewset_trait_is_implemented() {
197		fn assert_injectable<T: InjectableViewSet>() {}
198
199		// GenericViewSet should implement InjectableViewSet
200		assert_injectable::<GenericViewSet<()>>();
201	}
202}