1#[cfg(feature = "async")]
6use std::future::Future;
7#[cfg(feature = "async")]
8use std::pin::Pin;
9#[cfg(feature = "interface")]
10use std::sync::Arc;
11
12pub trait ModuleMeta: 'static {
14 const NAME: &'static str;
16
17 fn dependencies() -> &'static [(&'static str, std::any::TypeId)];
19}
20
21pub trait AutoBuilder: ModuleMeta {
25 type Capability: Clone + 'static;
27
28 type Error: std::error::Error + Send + 'static;
30
31 fn build(kit: &crate::kit::Kit) -> Result<Self::Capability, Self::Error>;
37}
38
39#[cfg(feature = "interface")]
46pub trait Interface: 'static {}
47
48#[cfg(feature = "interface")]
49impl<T: ?Sized + 'static> Interface for T {}
50
51#[cfg(feature = "interface")]
63pub trait InterfaceBuilder: ModuleMeta {
64 type Interface: ?Sized + 'static;
66
67 type Capability: Clone + 'static;
69
70 type Error: std::error::Error + Send + 'static;
72
73 fn build(kit: &crate::kit::Kit) -> Result<Self::Capability, Self::Error>;
79
80 fn into_interface(cap: Self::Capability) -> Arc<Self::Interface>;
85}
86
87#[cfg(feature = "async")]
104pub trait AsyncAutoBuilder: ModuleMeta {
105 type Capability: Clone + Send + Sync + 'static;
107
108 type Error: std::error::Error + Send + 'static;
110
111 #[allow(
121 clippy::type_complexity,
122 reason = "Pin<Box<dyn Future + Send>> is the canonical dyn-compatible async trait dispatch type"
123 )]
124 fn build<'a>(
125 kit: &'a crate::kit::AsyncKit,
126 ) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>>;
127}
128
129pub(crate) type BuildFn = Box<
134 dyn FnOnce(
135 &crate::kit::Kit,
136 ) -> Result<Box<dyn std::any::Any>, Box<dyn std::error::Error + Send + 'static>>,
137>;
138
139#[cfg(all(test, feature = "async"))]
140mod async_tests {
141 use super::*;
142 use crate::kit::AsyncKit;
143 use crate::test_helpers::{MockError, block_on};
144 use std::future::Future;
145 use std::pin::Pin;
146 use std::sync::Arc;
147
148 #[derive(Debug, Clone, PartialEq)]
149 struct LoggerCapability {
150 name: String,
151 }
152
153 struct MockLoggerModule;
154
155 impl ModuleMeta for MockLoggerModule {
156 const NAME: &'static str = "mock-logger";
157 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
158 &[]
159 }
160 }
161
162 impl AsyncAutoBuilder for MockLoggerModule {
163 type Capability = Arc<LoggerCapability>;
164 type Error = MockError;
165
166 fn build<'a>(
167 kit: &'a AsyncKit,
168 ) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>>
169 {
170 let _ = kit;
171 Box::pin(async move {
172 Ok(Arc::new(LoggerCapability {
173 name: "mock".to_string(),
174 }))
175 })
176 }
177 }
178
179 #[test]
180 fn async_auto_builder_returns_pin_box_future() {
181 let kit = AsyncKit::new();
182 let fut = MockLoggerModule::build(&kit);
183 let result = block_on(fut);
184 assert!(result.is_ok());
185 let cap = result.expect("build future returned Ok");
186 assert_eq!(cap.name, "mock");
187 }
188
189 #[test]
190 fn async_auto_builder_capability_is_send_sync() {
191 fn assert_send_sync<T: Send + Sync>() {}
192 assert_send_sync::<LoggerCapability>();
193 assert_send_sync::<Arc<LoggerCapability>>();
194 }
195
196 #[test]
197 fn async_auto_builder_error_is_send_static() {
198 fn assert_send_static<T: Send + 'static>() {}
199 assert_send_static::<MockError>();
200 }
201}
202
203#[cfg(all(test, feature = "interface"))]
204mod interface_tests {
205 use super::*;
206
207 #[test]
208 fn interface_auto_implemented_for_primitive_types() {
209 fn assert_interface<T: Interface>() {}
210 assert_interface::<i32>();
211 assert_interface::<u64>();
212 assert_interface::<String>();
213 assert_interface::<Vec<u8>>();
214 assert_interface::<bool>();
215 }
216
217 #[test]
218 fn interface_auto_implemented_for_custom_types() {
219 struct MyType;
220 #[allow(dead_code)]
221 enum MyEnum {
222 A,
223 B,
224 }
225
226 fn assert_interface<T: Interface>() {}
227 assert_interface::<MyType>();
228 assert_interface::<MyEnum>();
229 }
230
231 #[test]
232 fn interface_auto_implemented_for_reference_types() {
233 trait MyTrait {}
234 fn assert_interface<T: Interface + ?Sized>() {}
235 assert_interface::<dyn MyTrait>();
236 }
237}
238
239#[cfg(all(test, feature = "interface"))]
240mod interface_builder_tests {
241 use super::*;
242 use crate::kit::Kit;
243 use std::sync::atomic::{AtomicUsize, Ordering};
244 use std::sync::Arc;
245
246 trait Logger: 'static {
248 fn log(&self, msg: &str);
249 }
250
251 struct ConsoleLogger {
253 counter: AtomicUsize,
254 }
255
256 impl Logger for ConsoleLogger {
257 fn log(&self, msg: &str) {
258 let _ = msg;
259 self.counter.fetch_add(1, Ordering::Relaxed);
260 }
261 }
262
263 #[derive(Debug)]
265 struct InterfaceTestError;
266
267 impl std::fmt::Display for InterfaceTestError {
268 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269 write!(f, "interface test error")
270 }
271 }
272
273 impl std::error::Error for InterfaceTestError {}
274
275 struct ConsoleLoggerModule;
277
278 impl ModuleMeta for ConsoleLoggerModule {
279 const NAME: &'static str = "console-logger";
280 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
281 &[]
282 }
283 }
284
285 impl InterfaceBuilder for ConsoleLoggerModule {
286 type Interface = dyn Logger;
287 type Capability = Arc<ConsoleLogger>;
288 type Error = InterfaceTestError;
289
290 fn build(_kit: &Kit) -> Result<Arc<ConsoleLogger>, InterfaceTestError> {
291 Ok(Arc::new(ConsoleLogger {
292 counter: AtomicUsize::new(0),
293 }))
294 }
295
296 fn into_interface(cap: Arc<ConsoleLogger>) -> Arc<dyn Logger> {
297 cap
298 }
299 }
300
301 #[test]
302 fn interface_builder_build_returns_concrete_capability() {
303 let kit = Kit::new();
304 let cap = ConsoleLoggerModule::build(&kit).expect("build succeeds");
305 assert_eq!(Arc::strong_count(&cap), 1);
306 }
307
308 #[test]
309 fn interface_builder_into_interface_produces_trait_object() {
310 let kit = Kit::new();
311 let cap = ConsoleLoggerModule::build(&kit).expect("build succeeds");
312 let iface: Arc<dyn Logger> = ConsoleLoggerModule::into_interface(cap);
313 iface.log("hello");
314 iface.log("world");
315 }
316
317 #[test]
318 fn interface_builder_interface_type_is_dyn_compatible() {
319 fn assert_dyn_compatible<T: ?Sized + 'static>() {}
320 assert_dyn_compatible::<dyn Logger>();
321 }
322
323 #[test]
324 fn interface_builder_capability_is_clone() {
325 let cap = Arc::new(ConsoleLogger {
326 counter: AtomicUsize::new(0),
327 });
328 let cloned = cap.clone();
329 assert_eq!(Arc::strong_count(&cloned), 2);
330 drop(cap);
331 assert_eq!(Arc::strong_count(&cloned), 1);
332 }
333
334 #[test]
335 fn interface_builder_does_not_require_autobuilder() {
336 fn requires_interface_builder<T: InterfaceBuilder>() {}
342 requires_interface_builder::<ConsoleLoggerModule>();
343 }
344}