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