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 #[must_use]
22 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
23 &[]
24 }
25}
26
27pub trait AutoBuilder: ModuleMeta {
31 type Capability: Clone + 'static;
33
34 type Error: std::error::Error + Send + 'static;
36
37 fn build(kit: &crate::kit::Kit) -> Result<Self::Capability, Self::Error>;
43}
44
45#[cfg(feature = "interface")]
52pub trait Interface: 'static {}
53
54#[cfg(feature = "interface")]
55impl<T: ?Sized + 'static> Interface for T {}
56
57#[cfg(feature = "interface")]
69pub trait InterfaceBuilder: ModuleMeta {
70 type Interface: ?Sized + 'static;
72
73 type Capability: Clone + 'static;
75
76 type Error: std::error::Error + Send + 'static;
78
79 fn build(kit: &crate::kit::Kit) -> Result<Self::Capability, Self::Error>;
85
86 fn into_interface(cap: Self::Capability) -> Arc<Self::Interface>;
91}
92
93#[cfg(feature = "async")]
110pub trait AsyncAutoBuilder: ModuleMeta {
111 type Capability: Clone + Send + Sync + 'static;
113
114 type Error: std::error::Error + Send + 'static;
116
117 #[allow(
127 clippy::type_complexity,
128 reason = "Pin<Box<dyn Future + Send>> is the canonical dyn-compatible async trait dispatch type"
129 )]
130 fn build<'a>(
131 kit: &'a crate::kit::AsyncKit,
132 ) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>>;
133}
134
135pub(crate) type BuildFn = Box<
140 dyn FnOnce(
141 &crate::kit::Kit,
142 ) -> Result<Box<dyn std::any::Any>, Box<dyn std::error::Error + Send + 'static>>,
143>;
144
145#[cfg(all(test, feature = "async"))]
146mod async_tests {
147 use super::*;
148 use crate::kit::AsyncKit;
149 use crate::test_helpers::{MockError, block_on};
150 use std::future::Future;
151 use std::pin::Pin;
152 use std::sync::Arc;
153
154 #[derive(Debug, Clone, PartialEq)]
155 struct LoggerCapability {
156 name: String,
157 }
158
159 struct MockLoggerModule;
160
161 impl ModuleMeta for MockLoggerModule {
162 const NAME: &'static str = "mock-logger";
163 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
164 &[]
165 }
166 }
167
168 impl AsyncAutoBuilder for MockLoggerModule {
169 type Capability = Arc<LoggerCapability>;
170 type Error = MockError;
171
172 fn build<'a>(
173 kit: &'a AsyncKit,
174 ) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>>
175 {
176 let _ = kit;
177 Box::pin(async move {
178 Ok(Arc::new(LoggerCapability {
179 name: "mock".to_string(),
180 }))
181 })
182 }
183 }
184
185 #[test]
186 fn async_auto_builder_returns_pin_box_future() {
187 let kit = AsyncKit::new();
188 let fut = MockLoggerModule::build(&kit);
189 let result = block_on(fut);
190 assert!(result.is_ok());
191 let cap = result.expect("build future returned Ok");
192 assert_eq!(cap.name, "mock");
193 }
194
195 #[test]
196 fn async_auto_builder_capability_is_send_sync() {
197 fn assert_send_sync<T: Send + Sync>() {}
198 assert_send_sync::<LoggerCapability>();
199 assert_send_sync::<Arc<LoggerCapability>>();
200 }
201
202 #[test]
203 fn async_auto_builder_error_is_send_static() {
204 fn assert_send_static<T: Send + 'static>() {}
205 assert_send_static::<MockError>();
206 }
207
208 #[test]
209 fn mock_logger_module_dependencies_empty() {
210 let deps = MockLoggerModule::dependencies();
211 assert!(deps.is_empty());
212 }
213}
214
215#[cfg(all(test, feature = "interface"))]
216mod interface_tests {
217 use super::*;
218
219 #[test]
220 fn interface_auto_implemented_for_primitive_types() {
221 fn assert_interface<T: Interface>() {}
222 assert_interface::<i32>();
223 assert_interface::<u64>();
224 assert_interface::<String>();
225 assert_interface::<Vec<u8>>();
226 assert_interface::<bool>();
227 }
228
229 #[test]
230 fn interface_auto_implemented_for_custom_types() {
231 struct MyType;
232 #[allow(dead_code)]
233 enum MyEnum {
234 A,
235 B,
236 }
237
238 fn assert_interface<T: Interface>() {}
239 assert_interface::<MyType>();
240 assert_interface::<MyEnum>();
241 }
242
243 #[test]
244 fn interface_auto_implemented_for_reference_types() {
245 trait MyTrait {}
246 fn assert_interface<T: Interface + ?Sized>() {}
247 assert_interface::<dyn MyTrait>();
248 }
249}
250
251#[cfg(all(test, feature = "interface"))]
252mod interface_builder_tests {
253 use super::*;
254 use crate::kit::Kit;
255 use std::sync::Arc;
256 use std::sync::atomic::{AtomicUsize, Ordering};
257
258 trait Logger: 'static {
260 fn log(&self, msg: &str);
261 }
262
263 struct ConsoleLogger {
265 counter: AtomicUsize,
266 }
267
268 impl Logger for ConsoleLogger {
269 fn log(&self, msg: &str) {
270 let _ = msg;
271 self.counter.fetch_add(1, Ordering::Relaxed);
272 }
273 }
274
275 #[derive(Debug)]
277 struct InterfaceTestError;
278
279 impl std::fmt::Display for InterfaceTestError {
280 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281 write!(f, "interface test error")
282 }
283 }
284
285 impl std::error::Error for InterfaceTestError {}
286
287 struct ConsoleLoggerModule;
289
290 impl ModuleMeta for ConsoleLoggerModule {
291 const NAME: &'static str = "console-logger";
292 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
293 &[]
294 }
295 }
296
297 impl InterfaceBuilder for ConsoleLoggerModule {
298 type Interface = dyn Logger;
299 type Capability = Arc<ConsoleLogger>;
300 type Error = InterfaceTestError;
301
302 fn build(_kit: &Kit) -> Result<Arc<ConsoleLogger>, InterfaceTestError> {
303 Ok(Arc::new(ConsoleLogger {
304 counter: AtomicUsize::new(0),
305 }))
306 }
307
308 fn into_interface(cap: Arc<ConsoleLogger>) -> Arc<dyn Logger> {
309 cap
310 }
311 }
312
313 #[test]
314 fn interface_builder_build_returns_concrete_capability() {
315 let kit = Kit::new();
316 let cap = ConsoleLoggerModule::build(&kit).expect("build succeeds");
317 assert_eq!(Arc::strong_count(&cap), 1);
318 }
319
320 #[test]
321 fn interface_builder_into_interface_produces_trait_object() {
322 let kit = Kit::new();
323 let cap = ConsoleLoggerModule::build(&kit).expect("build succeeds");
324 let iface: Arc<dyn Logger> = ConsoleLoggerModule::into_interface(cap);
325 iface.log("hello");
326 iface.log("world");
327 }
328
329 #[test]
330 fn interface_builder_interface_type_is_dyn_compatible() {
331 fn assert_dyn_compatible<T: ?Sized + 'static>() {}
332 assert_dyn_compatible::<dyn Logger>();
333 }
334
335 #[test]
336 fn interface_builder_capability_is_clone() {
337 let cap = Arc::new(ConsoleLogger {
338 counter: AtomicUsize::new(0),
339 });
340 let cloned = cap.clone();
341 assert_eq!(Arc::strong_count(&cloned), 2);
342 drop(cap);
343 assert_eq!(Arc::strong_count(&cloned), 1);
344 }
345
346 #[test]
347 fn interface_builder_does_not_require_autobuilder() {
348 fn requires_interface_builder<T: InterfaceBuilder>() {}
354 requires_interface_builder::<ConsoleLoggerModule>();
355 }
356
357 #[test]
358 fn interface_test_error_display() {
359 let e = InterfaceTestError;
360 assert_eq!(format!("{e}"), "interface test error");
361 }
362
363 #[test]
364 fn console_logger_module_dependencies_empty() {
365 let deps = ConsoleLoggerModule::dependencies();
366 assert!(deps.is_empty());
367 }
368}
369
370#[cfg(test)]
371mod module_meta_tests {
372 use super::*;
373
374 struct NoDepsModule;
375 impl ModuleMeta for NoDepsModule {
376 const NAME: &'static str = "no-deps";
377 }
378
379 #[test]
380 fn default_dependencies_returns_empty_slice() {
381 let deps = NoDepsModule::dependencies();
382 assert!(deps.is_empty(), "default dependencies() should return &[]");
383 }
384}