1#[macro_export]
31macro_rules! impl_module_meta {
32 ($ty:ty, $name:literal) => {
33 impl $crate::core::ModuleMeta for $ty {
34 const NAME: &'static str = $name;
35
36 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
37 &[]
38 }
39 }
40 };
41 ($ty:ty, $name:literal, deps = [$($dep:ty),* $(,)?]) => {
42 impl $crate::core::ModuleMeta for $ty {
43 const NAME: &'static str = $name;
44
45 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
46 static DEPS: &[(&str, std::any::TypeId)] = &[
47 $( (<$dep as $crate::core::ModuleMeta>::NAME, std::any::TypeId::of::<$dep>()), )*
48 ];
49 DEPS
50 }
51 }
52 };
53}
54
55#[macro_export]
88macro_rules! impl_auto_builder {
89 ($ty:ty, $cap:ty, $err:ty, |$kit:ident| $body:expr) => {
90 impl $crate::core::AutoBuilder for $ty {
91 type Capability = $cap;
92 type Error = $err;
93
94 #[track_caller]
95 fn build(
96 $kit: &$crate::kit::Kit,
97 ) -> ::std::result::Result<Self::Capability, Self::Error> {
98 $body
99 }
100 }
101 };
102}
103
104#[cfg(feature = "async")]
144#[macro_export]
145macro_rules! impl_async_auto_builder {
146 ($ty:ty, $cap:ty, $err:ty, |$kit:ident| $body:expr) => {
147 impl $crate::core::AsyncAutoBuilder for $ty {
148 type Capability = $cap;
149 type Error = $err;
150
151 #[track_caller]
152 fn build<'a>(
153 $kit: &'a $crate::kit::AsyncKit,
154 ) -> ::std::pin::Pin<
155 ::std::boxed::Box<
156 dyn ::std::future::Future<
157 Output = ::std::result::Result<Self::Capability, Self::Error>,
158 > + Send
159 + 'a,
160 >,
161 > {
162 $body
163 }
164 }
165 };
166}
167
168#[cfg(test)]
169mod tests {
170 use crate::core::ModuleMeta;
171
172 struct MacroModuleNoDeps;
175 impl_module_meta!(MacroModuleNoDeps, "macro-no-deps");
176
177 struct Dep1;
178 impl_module_meta!(Dep1, "dep1");
179
180 struct Dep2;
181 impl_module_meta!(Dep2, "dep2");
182
183 struct MacroModuleWithDeps;
184 impl_module_meta!(MacroModuleWithDeps, "macro-with-deps", deps = [Dep1, Dep2]);
185
186 struct HandWrittenNoDeps;
189 impl ModuleMeta for HandWrittenNoDeps {
190 const NAME: &'static str = "macro-no-deps";
191 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
192 &[]
193 }
194 }
195
196 struct HandWrittenWithDeps;
197 impl ModuleMeta for HandWrittenWithDeps {
198 const NAME: &'static str = "macro-with-deps";
199 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
200 static DEPS: &[(&str, std::any::TypeId)] = &[
201 (<Dep1 as ModuleMeta>::NAME, std::any::TypeId::of::<Dep1>()),
202 (<Dep2 as ModuleMeta>::NAME, std::any::TypeId::of::<Dep2>()),
203 ];
204 DEPS
205 }
206 }
207
208 #[test]
211 fn macro_generates_correct_name_no_deps() {
212 assert_eq!(MacroModuleNoDeps::NAME, "macro-no-deps");
213 }
214
215 #[test]
216 fn macro_generates_empty_dependencies_when_no_deps() {
217 assert!(MacroModuleNoDeps::dependencies().is_empty());
218 }
219
220 #[test]
221 fn macro_generates_correct_name_with_deps() {
222 assert_eq!(MacroModuleWithDeps::NAME, "macro-with-deps");
223 }
224
225 #[test]
226 fn macro_generates_correct_dependency_count() {
227 assert_eq!(MacroModuleWithDeps::dependencies().len(), 2);
228 }
229
230 #[test]
231 fn macro_dependency_names_match_module_meta_names() {
232 let deps = MacroModuleWithDeps::dependencies();
233 assert_eq!(deps[0].0, "dep1");
234 assert_eq!(deps[1].0, "dep2");
235 }
236
237 #[test]
238 fn macro_dependency_type_ids_match_hand_written() {
239 let macro_deps = MacroModuleWithDeps::dependencies();
240 let hand_deps = HandWrittenWithDeps::dependencies();
241 assert_eq!(macro_deps.len(), hand_deps.len());
242 for (i, (m, h)) in macro_deps.iter().zip(hand_deps.iter()).enumerate() {
243 assert_eq!(m.0, h.0, "dep {i}: name mismatch");
244 assert_eq!(m.1, h.1, "dep {i}: TypeId mismatch");
245 }
246 }
247
248 #[test]
249 fn macro_name_equals_hand_written_name() {
250 assert_eq!(MacroModuleNoDeps::NAME, HandWrittenNoDeps::NAME);
251 assert_eq!(MacroModuleWithDeps::NAME, HandWrittenWithDeps::NAME);
252 }
253
254 #[test]
255 fn macro_dependencies_equal_hand_written_no_deps() {
256 let m = MacroModuleNoDeps::dependencies();
257 let h = HandWrittenNoDeps::dependencies();
258 assert_eq!(m.len(), h.len());
259 }
260}
261
262#[cfg(all(test, feature = "async"))]
263mod async_macro_tests {
264 use crate::core::{AsyncAutoBuilder, ModuleMeta};
265 use crate::kit::AsyncKit;
266 use crate::test_helpers::block_on;
267 use std::future::Future;
268 use std::pin::Pin;
269 use std::sync::Arc;
270 use thiserror::Error;
271
272 #[derive(Debug, Error)]
275 #[allow(dead_code, reason = "mock error type verifies trait signature only")]
276 enum MockErr {
277 #[error("mock async build failed: {0}")]
278 Failed(String),
279 }
280
281 #[derive(Clone, Debug, PartialEq, Eq)]
282 struct AsyncCap {
283 value: u32,
284 }
285
286 struct MacroAsyncModule;
288 impl_module_meta!(MacroAsyncModule, "macro-async");
289 impl_async_auto_builder!(MacroAsyncModule, Arc<AsyncCap>, MockErr, |kit| Box::pin(
290 async move {
291 let _ = kit;
292 Ok(Arc::new(AsyncCap { value: 42 }))
293 }
294 ));
295
296 struct HandAsyncModule;
298 impl ModuleMeta for HandAsyncModule {
299 const NAME: &'static str = "macro-async";
300 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
301 &[]
302 }
303 }
304 impl AsyncAutoBuilder for HandAsyncModule {
305 type Capability = Arc<AsyncCap>;
306 type Error = MockErr;
307 fn build<'a>(
308 kit: &'a AsyncKit,
309 ) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>>
310 {
311 let _ = kit;
312 Box::pin(async move { Ok(Arc::new(AsyncCap { value: 42 })) })
313 }
314 }
315
316 struct ErrAsyncModule;
318 impl_module_meta!(ErrAsyncModule, "err-async");
319 impl_async_auto_builder!(ErrAsyncModule, Arc<AsyncCap>, MockErr, |kit| Box::pin(
320 async move {
321 let _ = kit;
322 Err(MockErr::Failed("intentional".to_string()))
323 }
324 ));
325
326 #[test]
329 fn macro_async_generates_correct_name() {
330 assert_eq!(MacroAsyncModule::NAME, "macro-async");
331 }
332
333 #[test]
334 fn macro_async_generates_empty_dependencies() {
335 assert!(MacroAsyncModule::dependencies().is_empty());
336 }
337
338 #[test]
339 fn macro_async_capability_type_matches_hand_written() {
340 assert_eq!(
341 std::any::TypeId::of::<<MacroAsyncModule as AsyncAutoBuilder>::Capability>(),
342 std::any::TypeId::of::<<HandAsyncModule as AsyncAutoBuilder>::Capability>(),
343 );
344 }
345
346 #[test]
347 fn macro_async_error_type_matches_hand_written() {
348 assert_eq!(
349 std::any::TypeId::of::<<MacroAsyncModule as AsyncAutoBuilder>::Error>(),
350 std::any::TypeId::of::<<HandAsyncModule as AsyncAutoBuilder>::Error>(),
351 );
352 }
353
354 #[test]
355 fn macro_async_build_returns_expected_capability() {
356 let kit = AsyncKit::new();
357 let cap = block_on(MacroAsyncModule::build(&kit)).unwrap();
358 assert_eq!(cap.value, 42);
359 }
360
361 #[test]
362 fn macro_async_build_result_matches_hand_written() {
363 let kit = AsyncKit::new();
364 let macro_cap = block_on(MacroAsyncModule::build(&kit)).unwrap();
365 let hand_cap = block_on(HandAsyncModule::build(&kit)).unwrap();
366 assert_eq!(macro_cap, hand_cap);
367 }
368
369 #[test]
370 fn macro_async_build_propagates_errors() {
371 let kit = AsyncKit::new();
372 let result = block_on(ErrAsyncModule::build(&kit));
373 assert!(result.is_err());
374 }
375
376 #[test]
377 fn macro_async_name_equals_hand_written_name() {
378 assert_eq!(MacroAsyncModule::NAME, HandAsyncModule::NAME);
379 }
380
381 #[test]
382 fn hand_written_async_module_dependencies_empty() {
383 assert!(HandAsyncModule::dependencies().is_empty());
384 }
385}
386
387#[cfg(test)]
388mod sync_auto_builder_tests {
389 use crate::core::{AutoBuilder, ModuleMeta};
390 use crate::kit::Kit;
391 use std::sync::Arc;
392 use thiserror::Error;
393
394 #[derive(Debug, Error)]
397 #[allow(dead_code, reason = "mock error type verifies trait signature only")]
398 enum MockErr {
399 #[error("mock build failed: {0}")]
400 Failed(String),
401 }
402
403 #[derive(Clone, Debug, PartialEq, Eq)]
404 struct SyncCap {
405 value: u32,
406 }
407
408 struct MacroSyncModule;
410 impl_module_meta!(MacroSyncModule, "macro-sync");
411 impl_auto_builder!(MacroSyncModule, Arc<SyncCap>, MockErr, |_kit| Ok(Arc::new(
412 SyncCap { value: 42 }
413 )));
414
415 struct HandSyncModule;
417 impl ModuleMeta for HandSyncModule {
418 const NAME: &'static str = "macro-sync";
419 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
420 &[]
421 }
422 }
423 impl AutoBuilder for HandSyncModule {
424 type Capability = Arc<SyncCap>;
425 type Error = MockErr;
426 fn build(_kit: &Kit) -> Result<Self::Capability, Self::Error> {
427 Ok(Arc::new(SyncCap { value: 42 }))
428 }
429 }
430
431 struct ErrSyncModule;
433 impl_module_meta!(ErrSyncModule, "err-sync");
434 impl_auto_builder!(ErrSyncModule, Arc<SyncCap>, MockErr, |_kit| Err(
435 MockErr::Failed("intentional".to_string())
436 ));
437
438 #[test]
441 fn macro_sync_generates_correct_name() {
442 assert_eq!(MacroSyncModule::NAME, "macro-sync");
443 }
444
445 #[test]
446 fn macro_sync_generates_empty_dependencies() {
447 assert!(MacroSyncModule::dependencies().is_empty());
448 }
449
450 #[test]
451 fn macro_sync_capability_type_matches_hand_written() {
452 assert_eq!(
453 std::any::TypeId::of::<<MacroSyncModule as AutoBuilder>::Capability>(),
454 std::any::TypeId::of::<<HandSyncModule as AutoBuilder>::Capability>(),
455 );
456 }
457
458 #[test]
459 fn macro_sync_error_type_matches_hand_written() {
460 assert_eq!(
461 std::any::TypeId::of::<<MacroSyncModule as AutoBuilder>::Error>(),
462 std::any::TypeId::of::<<HandSyncModule as AutoBuilder>::Error>(),
463 );
464 }
465
466 #[test]
467 fn macro_sync_build_returns_expected_capability() {
468 let kit = Kit::new();
469 let cap = MacroSyncModule::build(&kit).unwrap();
470 assert_eq!(cap.value, 42);
471 }
472
473 #[test]
474 fn macro_sync_build_result_matches_hand_written() {
475 let kit = Kit::new();
476 let macro_cap = MacroSyncModule::build(&kit).unwrap();
477 let hand_cap = HandSyncModule::build(&kit).unwrap();
478 assert_eq!(macro_cap, hand_cap);
479 }
480
481 #[test]
482 fn macro_sync_build_propagates_errors() {
483 let kit = Kit::new();
484 let result = ErrSyncModule::build(&kit);
485 assert!(result.is_err());
486 }
487
488 #[test]
489 fn macro_sync_name_equals_hand_written_name() {
490 assert_eq!(MacroSyncModule::NAME, HandSyncModule::NAME);
491 }
492
493 #[test]
494 fn hand_written_sync_module_dependencies_empty() {
495 assert!(HandSyncModule::dependencies().is_empty());
496 }
497}