Skip to main content

trait_kit/core/
meta.rs

1// Copyright (c) 2026 Kirky.X
2// SPDX-License-Identifier: MIT
3//! Core traits for module declaration and dependency management.
4
5#[cfg(feature = "async")]
6use std::future::Future;
7#[cfg(feature = "async")]
8use std::pin::Pin;
9
10/// Metadata trait for module registration.
11pub trait ModuleMeta: 'static {
12    /// The diagnostic name of this module.
13    const NAME: &'static str;
14
15    /// Returns (name, `TypeId`) pairs for modules this module depends on.
16    fn dependencies() -> &'static [(&'static str, std::any::TypeId)];
17}
18
19/// Builder trait for module construction.
20///
21/// Implemented by the user for each module.
22pub trait AutoBuilder: ModuleMeta {
23    /// The capability type this module provides. Must be Clone.
24    type Capability: Clone + 'static;
25
26    /// The error type returned on build failure.
27    type Error: std::error::Error + Send + 'static;
28
29    /// Build the module's capability using the provided Kit.
30    ///
31    /// # Errors
32    ///
33    /// Returns `Self::Error` if the module fails to build.
34    fn build(kit: &crate::kit::Kit) -> Result<Self::Capability, Self::Error>;
35}
36
37/// Async builder trait for module construction in async context.
38///
39/// Async counterpart of [`AutoBuilder`]. Implement this for modules requiring
40/// async initialization (database pools, HTTP clients, cache backends).
41///
42/// The `build` method returns a `Pin<Box<dyn Future + Send>>` rather than using
43/// native `async fn` in trait so that the trait can be type-erased through the
44/// `AsyncBuildFn` stored in `AsyncKit`'s dependency graph (Phase 1b). Rust
45/// 1.91 supports `async fn` in trait (stable since 1.75), but `dyn`-compatible
46/// dispatch still requires the explicit `Pin<Box>` indirection.
47///
48/// Compared to [`AutoBuilder`], the associated types tighten bounds:
49/// - `Capability: Clone + Send + Sync + 'static` (cross-thread sharing).
50/// - `Error: std::error::Error + Send + 'static` (cross-thread error propagation).
51///
52/// Requires the `async` feature on the crate.
53#[cfg(feature = "async")]
54pub trait AsyncAutoBuilder: ModuleMeta {
55    /// The capability type this module provides. Must be `Clone + Send + Sync`.
56    type Capability: Clone + Send + Sync + 'static;
57
58    /// The error type returned on build failure. Must be `Send + 'static`.
59    type Error: std::error::Error + Send + 'static;
60
61    /// Build the module's capability using the provided `AsyncKit`.
62    ///
63    /// The returned future borrows the kit for lifetime `'a`, allowing
64    /// the build callback to read configs / require dependencies from the kit
65    /// during async construction.
66    ///
67    /// # Errors
68    ///
69    /// Returns `Self::Error` if the module fails to build.
70    #[allow(
71        clippy::type_complexity,
72        reason = "Pin<Box<dyn Future + Send>> is the canonical dyn-compatible async trait dispatch type"
73    )]
74    fn build<'a>(
75        kit: &'a crate::kit::AsyncKit,
76    ) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>>;
77}
78
79/// Type-erased build function stored in the dependency graph.
80///
81/// Takes `&Kit<Unbuilt>` (same memory layout as `&Kit<Ready>`)
82/// because during the build phase we only have the unbuilt Kit.
83pub(crate) type BuildFn = Box<
84    dyn FnOnce(
85        &crate::kit::Kit,
86    ) -> Result<Box<dyn std::any::Any>, Box<dyn std::error::Error + Send + 'static>>,
87>;
88
89#[cfg(all(test, feature = "async"))]
90mod async_tests {
91    use super::*;
92    use crate::kit::AsyncKit;
93    use crate::test_helpers::{MockError, block_on};
94    use std::future::Future;
95    use std::pin::Pin;
96    use std::sync::Arc;
97
98    #[derive(Debug, Clone, PartialEq)]
99    struct LoggerCapability {
100        name: String,
101    }
102
103    struct MockLoggerModule;
104
105    impl ModuleMeta for MockLoggerModule {
106        const NAME: &'static str = "mock-logger";
107        fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
108            &[]
109        }
110    }
111
112    impl AsyncAutoBuilder for MockLoggerModule {
113        type Capability = Arc<LoggerCapability>;
114        type Error = MockError;
115
116        fn build<'a>(
117            kit: &'a AsyncKit,
118        ) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>>
119        {
120            let _ = kit;
121            Box::pin(async move {
122                Ok(Arc::new(LoggerCapability {
123                    name: "mock".to_string(),
124                }))
125            })
126        }
127    }
128
129    #[test]
130    fn async_auto_builder_returns_pin_box_future() {
131        let kit = AsyncKit::new();
132        let fut = MockLoggerModule::build(&kit);
133        let result = block_on(fut);
134        assert!(result.is_ok());
135        let cap = result.expect("build future returned Ok");
136        assert_eq!(cap.name, "mock");
137    }
138
139    #[test]
140    fn async_auto_builder_capability_is_send_sync() {
141        fn assert_send_sync<T: Send + Sync>() {}
142        assert_send_sync::<LoggerCapability>();
143        assert_send_sync::<Arc<LoggerCapability>>();
144    }
145
146    #[test]
147    fn async_auto_builder_error_is_send_static() {
148        fn assert_send_static<T: Send + 'static>() {}
149        assert_send_static::<MockError>();
150    }
151}