Skip to main content

reinhardt_di/
function_handle.rs

1//! Function handle for fluent override API
2//!
3//! This module provides `FunctionHandle`, a fluent API for setting
4//! and managing dependency overrides keyed by function pointer.
5
6use std::marker::PhantomData;
7
8use crate::InjectionContext;
9
10/// A handle for managing function-based dependency overrides.
11///
12/// `FunctionHandle` provides a fluent API for setting overrides on specific
13/// injectable functions. This is useful in tests where you want to mock
14/// specific dependency factories while leaving others unaffected.
15///
16/// # Type Parameters
17///
18/// * `'a` - Lifetime of the borrowed `InjectionContext`
19/// * `O` - The output type of the function (the dependency type)
20///
21/// # Examples
22///
23/// ```rust,no_run
24/// use reinhardt_di::{InjectionContext, SingletonScope};
25/// use std::sync::Arc;
26///
27/// # #[derive(Clone)]
28/// # struct Database;
29/// # impl Database {
30/// #     fn connect(_url: &str) -> Self { Database }
31/// #     fn mock() -> Self { Database }
32/// # }
33/// # fn create_database() -> Database { Database::connect("production://db") }
34///
35/// let singleton = Arc::new(SingletonScope::new());
36/// let ctx = InjectionContext::builder(singleton).build();
37///
38/// // Set override using fluent API
39/// ctx.dependency(create_database).override_with(Database::mock());
40/// ```
41pub struct FunctionHandle<'a, O> {
42	ctx: &'a InjectionContext,
43	func_ptr: usize,
44	_marker: PhantomData<O>,
45}
46
47impl<'a, O: Clone + Send + Sync + 'static> FunctionHandle<'a, O> {
48	/// Creates a new function handle.
49	///
50	/// This is typically called internally by `InjectionContext::dependency`.
51	pub(crate) fn new(ctx: &'a InjectionContext, func_ptr: usize) -> Self {
52		Self {
53			ctx,
54			func_ptr,
55			_marker: PhantomData,
56		}
57	}
58
59	/// Sets an override value for this function.
60	///
61	/// When the injectable function is invoked, this override value will be
62	/// returned instead of calling the actual function implementation.
63	///
64	/// # Arguments
65	///
66	/// * `value` - The value to return when this function is called
67	///
68	/// # Returns
69	///
70	/// A reference to self for method chaining.
71	///
72	/// # Examples
73	///
74	/// ```rust,no_run
75	/// # use reinhardt_di::{InjectionContext, SingletonScope};
76	/// # use std::sync::Arc;
77	/// # #[derive(Clone)]
78	/// # struct Database;
79	/// # fn create_database() -> Database { Database }
80	/// # let singleton = Arc::new(SingletonScope::new());
81	/// # let ctx = InjectionContext::builder(singleton).build();
82	/// # let mock_database = Database;
83	/// ctx.dependency(create_database)
84	///    .override_with(mock_database);
85	/// ```
86	pub fn override_with(&self, value: O) -> &Self {
87		self.ctx.overrides().set(self.func_ptr, value);
88		self
89	}
90
91	/// Clears the override for this function.
92	///
93	/// After calling this method, the actual function implementation will be
94	/// used when resolving this dependency.
95	///
96	/// # Returns
97	///
98	/// A reference to self for method chaining.
99	///
100	/// # Examples
101	///
102	/// ```rust,no_run
103	/// # use reinhardt_di::{InjectionContext, SingletonScope};
104	/// # use std::sync::Arc;
105	/// # #[derive(Clone)]
106	/// # struct Database;
107	/// # fn create_database() -> Database { Database }
108	/// # let singleton = Arc::new(SingletonScope::new());
109	/// # let ctx = InjectionContext::builder(singleton).build();
110	/// # let mock_database = Database;
111	/// // Set override
112	/// ctx.dependency(create_database).override_with(mock_database);
113	///
114	/// // Later, clear it
115	/// ctx.dependency(create_database).clear_override();
116	/// ```
117	pub fn clear_override(&self) -> &Self {
118		self.ctx.overrides().remove(self.func_ptr);
119		self
120	}
121
122	/// Checks if an override exists for this function.
123	///
124	/// # Returns
125	///
126	/// `true` if an override is set, `false` otherwise.
127	///
128	/// # Examples
129	///
130	/// ```rust,no_run
131	/// # use reinhardt_di::{InjectionContext, SingletonScope};
132	/// # use std::sync::Arc;
133	/// # #[derive(Clone)]
134	/// # struct Database;
135	/// # fn create_database() -> Database { Database }
136	/// # let singleton = Arc::new(SingletonScope::new());
137	/// # let ctx = InjectionContext::builder(singleton).build();
138	/// # let mock_database = Database;
139	/// assert!(!ctx.dependency(create_database).has_override());
140	/// ctx.dependency(create_database).override_with(mock_database);
141	/// assert!(ctx.dependency(create_database).has_override());
142	/// ```
143	pub fn has_override(&self) -> bool {
144		self.ctx.overrides().has(self.func_ptr)
145	}
146
147	/// Gets the current override value for this function, if any.
148	///
149	/// # Returns
150	///
151	/// `Some(value)` if an override is set, `None` otherwise.
152	///
153	/// # Examples
154	///
155	/// ```rust,no_run
156	/// # use reinhardt_di::{InjectionContext, SingletonScope};
157	/// # use std::sync::Arc;
158	/// # #[derive(Clone, PartialEq, Debug)]
159	/// # struct Database;
160	/// # fn create_database() -> Database { Database }
161	/// # let singleton = Arc::new(SingletonScope::new());
162	/// # let ctx = InjectionContext::builder(singleton).build();
163	/// # let mock_database = Database;
164	/// ctx.dependency(create_database).override_with(mock_database.clone());
165	/// let value = ctx.dependency(create_database).get_override();
166	/// assert_eq!(value, Some(mock_database));
167	/// ```
168	pub fn get_override(&self) -> Option<O> {
169		self.ctx.overrides().get(self.func_ptr)
170	}
171
172	/// Returns the function pointer address for this handle.
173	///
174	/// This is primarily useful for debugging or advanced use cases.
175	pub fn func_ptr(&self) -> usize {
176		self.func_ptr
177	}
178}
179
180#[cfg(test)]
181mod tests {
182	use std::sync::Arc;
183
184	use crate::{InjectionContext, SingletonScope};
185
186	fn create_string() -> String {
187		"production".to_string()
188	}
189
190	fn create_other_string() -> String {
191		"other_production".to_string()
192	}
193
194	#[test]
195	fn test_override_with() {
196		let singleton = Arc::new(SingletonScope::new());
197		let ctx = InjectionContext::builder(singleton).build();
198
199		ctx.dependency(create_string)
200			.override_with("mock".to_string());
201
202		assert!(ctx.dependency(create_string).has_override());
203		assert_eq!(
204			ctx.dependency(create_string).get_override(),
205			Some("mock".to_string())
206		);
207	}
208
209	#[test]
210	fn test_clear_override() {
211		let singleton = Arc::new(SingletonScope::new());
212		let ctx = InjectionContext::builder(singleton).build();
213
214		ctx.dependency(create_string)
215			.override_with("mock".to_string());
216		assert!(ctx.dependency(create_string).has_override());
217
218		ctx.dependency(create_string).clear_override();
219		assert!(!ctx.dependency(create_string).has_override());
220	}
221
222	#[test]
223	fn test_different_functions_independent() {
224		let singleton = Arc::new(SingletonScope::new());
225		let ctx = InjectionContext::builder(singleton).build();
226
227		ctx.dependency(create_string)
228			.override_with("mock_1".to_string());
229		ctx.dependency(create_other_string)
230			.override_with("mock_2".to_string());
231
232		assert_eq!(
233			ctx.dependency(create_string).get_override(),
234			Some("mock_1".to_string())
235		);
236		assert_eq!(
237			ctx.dependency(create_other_string).get_override(),
238			Some("mock_2".to_string())
239		);
240	}
241
242	#[test]
243	fn test_func_ptr() {
244		let singleton = Arc::new(SingletonScope::new());
245		let ctx = InjectionContext::builder(singleton).build();
246
247		let handle = ctx.dependency(create_string);
248		assert_eq!(handle.func_ptr(), create_string as usize);
249	}
250}