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 $((stringify!($dep), std::any::TypeId::of::<$dep>()),)*
48 ];
49 DEPS
50 }
51 }
52 };
53}
54
55#[cfg(feature = "async")]
95#[macro_export]
96macro_rules! impl_async_auto_builder {
97 ($ty:ty, $cap:ty, $err:ty, |$kit:ident| $body:expr) => {
98 impl $crate::core::AsyncAutoBuilder for $ty {
99 type Capability = $cap;
100 type Error = $err;
101
102 fn build<'a>(
103 $kit: &'a $crate::kit::AsyncKit,
104 ) -> ::std::pin::Pin<
105 ::std::boxed::Box<
106 dyn ::std::future::Future<
107 Output = ::std::result::Result<Self::Capability, Self::Error>,
108 > + Send
109 + 'a,
110 >,
111 > {
112 $body
113 }
114 }
115 };
116}
117
118#[cfg(test)]
119mod tests {
120 use crate::core::ModuleMeta;
121
122 struct MacroModuleNoDeps;
125 impl_module_meta!(MacroModuleNoDeps, "macro-no-deps");
126
127 struct Dep1;
128 impl_module_meta!(Dep1, "dep1");
129
130 struct Dep2;
131 impl_module_meta!(Dep2, "dep2");
132
133 struct MacroModuleWithDeps;
134 impl_module_meta!(MacroModuleWithDeps, "macro-with-deps", deps = [Dep1, Dep2]);
135
136 struct HandWrittenNoDeps;
139 impl ModuleMeta for HandWrittenNoDeps {
140 const NAME: &'static str = "macro-no-deps";
141 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
142 &[]
143 }
144 }
145
146 struct HandWrittenWithDeps;
147 impl ModuleMeta for HandWrittenWithDeps {
148 const NAME: &'static str = "macro-with-deps";
149 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
150 static DEPS: &[(&str, std::any::TypeId)] = &[
151 ("Dep1", std::any::TypeId::of::<Dep1>()),
152 ("Dep2", std::any::TypeId::of::<Dep2>()),
153 ];
154 DEPS
155 }
156 }
157
158 #[test]
161 fn macro_generates_correct_name_no_deps() {
162 assert_eq!(MacroModuleNoDeps::NAME, "macro-no-deps");
163 }
164
165 #[test]
166 fn macro_generates_empty_dependencies_when_no_deps() {
167 assert!(MacroModuleNoDeps::dependencies().is_empty());
168 }
169
170 #[test]
171 fn macro_generates_correct_name_with_deps() {
172 assert_eq!(MacroModuleWithDeps::NAME, "macro-with-deps");
173 }
174
175 #[test]
176 fn macro_generates_correct_dependency_count() {
177 assert_eq!(MacroModuleWithDeps::dependencies().len(), 2);
178 }
179
180 #[test]
181 fn macro_dependency_names_match_stringified_types() {
182 let deps = MacroModuleWithDeps::dependencies();
183 assert_eq!(deps[0].0, "Dep1");
184 assert_eq!(deps[1].0, "Dep2");
185 }
186
187 #[test]
188 fn macro_dependency_type_ids_match_hand_written() {
189 let macro_deps = MacroModuleWithDeps::dependencies();
190 let hand_deps = HandWrittenWithDeps::dependencies();
191 assert_eq!(macro_deps.len(), hand_deps.len());
192 for (i, (m, h)) in macro_deps.iter().zip(hand_deps.iter()).enumerate() {
193 assert_eq!(m.0, h.0, "dep {i}: name mismatch");
194 assert_eq!(m.1, h.1, "dep {i}: TypeId mismatch");
195 }
196 }
197
198 #[test]
199 fn macro_name_equals_hand_written_name() {
200 assert_eq!(MacroModuleNoDeps::NAME, HandWrittenNoDeps::NAME);
201 assert_eq!(MacroModuleWithDeps::NAME, HandWrittenWithDeps::NAME);
202 }
203
204 #[test]
205 fn macro_dependencies_equal_hand_written_no_deps() {
206 let m = MacroModuleNoDeps::dependencies();
207 let h = HandWrittenNoDeps::dependencies();
208 assert_eq!(m.len(), h.len());
209 }
210}
211
212#[cfg(all(test, feature = "async"))]
213mod async_macro_tests {
214 use crate::core::{AsyncAutoBuilder, ModuleMeta};
215 use crate::kit::AsyncKit;
216 use crate::test_helpers::block_on;
217 use std::future::Future;
218 use std::pin::Pin;
219 use std::sync::Arc;
220 use thiserror::Error;
221
222 #[derive(Debug, Error)]
225 #[allow(dead_code, reason = "mock error type verifies trait signature only")]
226 enum MockErr {
227 #[error("mock async build failed: {0}")]
228 Failed(String),
229 }
230
231 #[derive(Clone, Debug, PartialEq, Eq)]
232 struct AsyncCap {
233 value: u32,
234 }
235
236 struct MacroAsyncModule;
238 impl_module_meta!(MacroAsyncModule, "macro-async");
239 impl_async_auto_builder!(MacroAsyncModule, Arc<AsyncCap>, MockErr, |kit| Box::pin(
240 async move {
241 let _ = kit;
242 Ok(Arc::new(AsyncCap { value: 42 }))
243 }
244 ));
245
246 struct HandAsyncModule;
248 impl ModuleMeta for HandAsyncModule {
249 const NAME: &'static str = "macro-async";
250 fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
251 &[]
252 }
253 }
254 impl AsyncAutoBuilder for HandAsyncModule {
255 type Capability = Arc<AsyncCap>;
256 type Error = MockErr;
257 fn build<'a>(
258 kit: &'a AsyncKit,
259 ) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>>
260 {
261 let _ = kit;
262 Box::pin(async move { Ok(Arc::new(AsyncCap { value: 42 })) })
263 }
264 }
265
266 struct ErrAsyncModule;
268 impl_module_meta!(ErrAsyncModule, "err-async");
269 impl_async_auto_builder!(ErrAsyncModule, Arc<AsyncCap>, MockErr, |kit| Box::pin(
270 async move {
271 let _ = kit;
272 Err(MockErr::Failed("intentional".to_string()))
273 }
274 ));
275
276 #[test]
279 fn macro_async_generates_correct_name() {
280 assert_eq!(MacroAsyncModule::NAME, "macro-async");
281 }
282
283 #[test]
284 fn macro_async_generates_empty_dependencies() {
285 assert!(MacroAsyncModule::dependencies().is_empty());
286 }
287
288 #[test]
289 fn macro_async_capability_type_matches_hand_written() {
290 assert_eq!(
291 std::any::TypeId::of::<<MacroAsyncModule as AsyncAutoBuilder>::Capability>(),
292 std::any::TypeId::of::<<HandAsyncModule as AsyncAutoBuilder>::Capability>(),
293 );
294 }
295
296 #[test]
297 fn macro_async_error_type_matches_hand_written() {
298 assert_eq!(
299 std::any::TypeId::of::<<MacroAsyncModule as AsyncAutoBuilder>::Error>(),
300 std::any::TypeId::of::<<HandAsyncModule as AsyncAutoBuilder>::Error>(),
301 );
302 }
303
304 #[test]
305 fn macro_async_build_returns_expected_capability() {
306 let kit = AsyncKit::new();
307 let cap = block_on(MacroAsyncModule::build(&kit)).unwrap();
308 assert_eq!(cap.value, 42);
309 }
310
311 #[test]
312 fn macro_async_build_result_matches_hand_written() {
313 let kit = AsyncKit::new();
314 let macro_cap = block_on(MacroAsyncModule::build(&kit)).unwrap();
315 let hand_cap = block_on(HandAsyncModule::build(&kit)).unwrap();
316 assert_eq!(macro_cap, hand_cap);
317 }
318
319 #[test]
320 fn macro_async_build_propagates_errors() {
321 let kit = AsyncKit::new();
322 let result = block_on(ErrAsyncModule::build(&kit));
323 assert!(result.is_err());
324 }
325
326 #[test]
327 fn macro_async_name_equals_hand_written_name() {
328 assert_eq!(MacroAsyncModule::NAME, HandAsyncModule::NAME);
329 }
330}