Skip to main content

trait_kit/
lib.rs

1// Copyright (c) 2026 Kirky.X
2// SPDX-License-Identifier: MIT
3//! trait-kit — 模块标准接口与能力管理中心
4//!
5//! 提供模块定义标准接口和 Kit 能力管理中心的轻量实现。
6
7#![deny(unsafe_code)]
8#![warn(clippy::all, clippy::pedantic)]
9#![allow(clippy::module_name_repetitions)]
10#![doc = include_str!("../README.md")]
11
12pub mod core;
13mod error;
14pub mod kit;
15
16pub mod i18n;
17
18pub mod prelude;
19
20pub use error::TraitKitError;
21pub use error::TraitKitResult;
22
23#[cfg(feature = "async")]
24pub use core::AsyncAutoBuilder;
25#[cfg(feature = "async")]
26pub use kit::{AsyncKit, AsyncReady, AsyncUnbuilt};
27
28#[cfg(all(feature = "lifecycle", feature = "async"))]
29pub use core::AsyncLifecycle;
30#[cfg(feature = "lifecycle")]
31pub use core::Lifecycle;
32
33#[cfg(all(feature = "health", feature = "async"))]
34pub use core::AsyncHealthCheck;
35#[cfg(feature = "health")]
36pub use core::{HealthCheck, HealthStatus};
37
38#[cfg(feature = "observability")]
39pub use core::BuildObserver;
40
41#[cfg(all(feature = "scope", feature = "async"))]
42pub use kit::AsyncScope;
43#[cfg(feature = "scope")]
44pub use kit::Scope;
45
46/// Shared test helpers for async test modules (`block_on` executor + `MockError`).
47///
48/// Extracted to deduplicate between `core::meta::async_tests` and
49/// `kit::async_kit::tests` (audit LOW-003). Gated on `async` feature because
50/// both consumer test mods are `#[cfg(all(test, feature = "async"))]`.
51#[cfg(all(test, feature = "async"))]
52pub(crate) mod test_helpers {
53    use std::future::Future;
54    use std::task::{self, Poll};
55
56    /// Minimal single-threaded `Future` executor for tests (no extra deps).
57    ///
58    /// Uses `Waker::noop()` (stable since 1.85) because the `async` feature
59    /// deliberately stays dep-free (no `tokio` / `futures` test runtime).
60    ///
61    /// # Panics
62    ///
63    /// Panics if the future does not complete within `MAX_POLLS` iterations,
64    /// preventing infinite loops from hanging the test suite.
65    pub(crate) fn block_on<F: Future>(future: F) -> F::Output {
66        /// Maximum number of poll iterations before panicking.
67        /// Generous enough for any reasonable test future.
68        const MAX_POLLS: u32 = 1_000_000;
69
70        let waker = task::Waker::noop();
71        // `Context::from_waker` takes `&Waker`; the borrow is required by the
72        // API signature (not a clippy false positive).
73        #[allow(clippy::needless_borrow)]
74        let mut cx = task::Context::from_waker(&waker);
75        let mut future = std::pin::pin!(future);
76        let mut polls = 0u32;
77        loop {
78            match future.as_mut().poll(&mut cx) {
79                Poll::Ready(v) => return v,
80                Poll::Pending => {
81                    polls += 1;
82                    if polls >= MAX_POLLS {
83                        panic!(
84                            "block_on: future did not complete within \
85                             {MAX_POLLS} poll iterations (possible infinite loop)"
86                        );
87                    }
88                    std::hint::spin_loop();
89                }
90            }
91        }
92    }
93
94    /// Mock error type for tests verifying `AsyncAutoBuilder` trait signatures.
95    #[derive(Debug, thiserror::Error)]
96    #[allow(dead_code, reason = "mock error type verifies trait signature only")]
97    pub(crate) enum MockError {
98        #[error("mock build failed: {0}")]
99        Failed(String),
100    }
101
102    #[cfg(test)]
103    mod block_on_tests {
104        use super::*;
105        use std::sync::atomic::{AtomicU32, Ordering};
106        use std::task::Poll;
107
108        #[test]
109        fn block_on_handles_pending() {
110            static POLL_COUNT: AtomicU32 = AtomicU32::new(0);
111            POLL_COUNT.store(0, Ordering::SeqCst);
112
113            let result = block_on(std::future::poll_fn(|_cx| {
114                let n = POLL_COUNT.fetch_add(1, Ordering::SeqCst);
115                if n < 1 {
116                    Poll::Pending
117                } else {
118                    Poll::Ready(42)
119                }
120            }));
121            assert_eq!(result, 42);
122            assert!(POLL_COUNT.load(Ordering::SeqCst) >= 2);
123        }
124    }
125}