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}