Skip to main content

wecomx_transport/common/
extensions.rs

1//! Type-indexed per-request extension bag — arbitrary caller-defined config
2//! threaded from Transport defaults and request builders down to
3//! TransportBackend::execute.
4//!
5//! # 约定(CONVENTIONS)
6//!
7//! - **敏感值**:`Extensions` 的 `Debug` 会逐值输出 `Debug`。敏感配置
8//!   (token、密钥)的值类型必须自行实现脱敏 `Debug`(如输出
9//!   `"<redacted>"`),思路对齐 [`MaskedHeaders`](crate::MaskedHeaders)。
10//! - **不可序列化**:袋内值可含回调 / 句柄,`Extensions` 整体不可序列化;
11//!   不要把 `Extensions` 放进任何 serde 结构。
12//! - **同型多值**:同一 `TypeId` 后写覆盖先写。需要「同型多值」时以
13//!   `Vec<T>` / map newtype 为值类型。
14
15use std::any::{Any, TypeId};
16use std::collections::HashMap;
17use std::fmt::Debug;
18use std::sync::Arc;
19
20/// A value storable in [`Extensions`]. Blanket-implemented — no manual impl.
21///
22/// Any `'static` type that is `Debug + Send + Sync` qualifies; `Clone` is
23/// **not** required (the bag shares entries via `Arc`).
24pub trait Extension: Any + Debug + Send + Sync + 'static {
25    /// Return the value as `&dyn Any` for typed downcasts.
26    fn as_any(&self) -> &dyn Any;
27}
28
29impl<T: Any + Debug + Send + Sync + 'static> Extension for T {
30    fn as_any(&self) -> &dyn Any {
31        self
32    }
33}
34
35/// Type-indexed bag of arbitrary request-scoped configuration.
36///
37/// Cheap to clone (Arc-shared entries). Merge semantics: per-TypeId override
38/// (later layer wins). One value per type — to stack multiple values of the
39/// "same kind", store a `Vec<T>` / map newtype as the value.
40///
41/// # Example
42///
43/// ```ignore
44/// #[derive(Debug)]
45/// pub struct RetryConfig { pub max_retries: u32 }
46///
47/// let mut ext = wecomx_transport::Extensions::new();
48/// ext.insert(RetryConfig { max_retries: 3 });
49/// assert_eq!(ext.get::<RetryConfig>().unwrap().max_retries, 3);
50/// ```
51#[derive(Clone, Default)]
52pub struct Extensions {
53    map: HashMap<TypeId, Arc<dyn Extension>>,
54}
55
56impl Extensions {
57    /// Create an empty bag.
58    pub fn new() -> Self {
59        Self::default()
60    }
61
62    /// True when the bag holds no entries.
63    pub fn is_empty(&self) -> bool {
64        self.map.is_empty()
65    }
66
67    /// Number of entries in the bag.
68    pub fn len(&self) -> usize {
69        self.map.len()
70    }
71
72    /// Insert (or replace) a value keyed by its concrete type.
73    ///
74    /// Same-type re-insert overrides the previous value (last wins) and
75    /// returns the previously stored value, if any.
76    pub fn insert<T>(&mut self, value: T) -> Option<Arc<dyn Extension>>
77    where
78        T: Any + Debug + Send + Sync + 'static,
79    {
80        self.map.insert(TypeId::of::<T>(), Arc::new(value))
81    }
82
83    /// Builder-style insert. Same-type re-insert overrides (last wins).
84    #[must_use]
85    pub fn with<T>(mut self, value: T) -> Self
86    where
87        T: Any + Debug + Send + Sync + 'static,
88    {
89        self.insert(value);
90        self
91    }
92
93    /// Typed read. Custom transports call this in `execute`.
94    ///
95    /// Returns `None` when no value of the given concrete type is present.
96    pub fn get<T>(&self) -> Option<&T>
97    where
98        T: Any + Send + Sync + 'static,
99    {
100        self.map
101            .get(&TypeId::of::<T>())
102            .and_then(|arc| arc.as_ref().as_any().downcast_ref::<T>())
103    }
104
105    /// Whether a value of the given concrete type is present.
106    pub fn contains<T>(&self) -> bool
107    where
108        T: Any + Send + Sync + 'static,
109    {
110        self.map.contains_key(&TypeId::of::<T>())
111    }
112
113    /// Remove and return the value of the given concrete type, if any.
114    pub fn remove<T>(&mut self) -> Option<Arc<dyn Extension>>
115    where
116        T: Any + Send + Sync + 'static,
117    {
118        self.map.remove(&TypeId::of::<T>())
119    }
120
121    /// Merge `other` into `self`; per-TypeId, `other` wins.
122    ///
123    /// This is the「叠加」语义:逐层调用即逐层覆盖。Entries are shared via
124    /// `Arc` — no deep copy.
125    pub fn extend(&mut self, other: &Extensions) {
126        for (k, v) in &other.map {
127            self.map.insert(*k, Arc::clone(v));
128        }
129    }
130}
131
132impl Debug for Extensions {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        f.debug_set().entries(self.map.values()).finish()
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    //! ## 模块摘要:Extensions(TypeId 索引能力袋)
141    //!
142    //! ### 关键接口
143    //! - [Extensions::insert] / [Extensions::get] — 按具体类型插入 / 读取
144    //! - [Extensions::with] — builder 风格插入
145    //! - [Extensions::extend] — 合并语义(other 覆盖同型)
146    //! - [Extensions::remove] / [Extensions::contains] — 删除 / 判断存在
147    //!
148    //! ### 关键分支与异常路径
149    //! - 同 TypeId 后写覆盖先写
150    //! - 空袋 is_empty / len == 0
151    //! - Clone 后袋内值 Arc 共享
152    //! - 非 Clone 值类型(仅 Debug + Send + Sync)可存入读取
153    //!
154    //! ### 上下游交互
155    //! - 上游:TransportBuilder / Transport 默认袋与 wecomx crate 的 CliRun /
156    //!   请求 builder 通过 `extension()` / `extensions()` 注入
157    //! - 下游:自定义 TransportBackend 在 `execute` 中
158    //!   `options.extensions.get::<T>()` 读取
159
160    use super::*;
161
162    /// P0:[Extensions::insert] / [Extensions::get] 同型值往返可读
163    /// 条件:插入 `#[derive(Debug)] struct MockVal(u32)` 后按同类型读取
164    /// 断言:get 返回的引用值正确(字段为 42)
165    #[test]
166    fn insert_then_get_roundtrip() {
167        let mut ext = Extensions::new();
168        ext.insert(MockVal(42));
169        assert_eq!(ext.get::<MockVal>().unwrap().0, 42);
170    }
171
172    /// P0:[Extensions::insert] 同 TypeId 后写覆盖先写
173    /// 条件:连续插入两个 MockVal(值 1 与 2),再按同类型读取
174    /// 断言:get 返回值为后插入的 2
175    #[test]
176    fn insert_same_type_overrides() {
177        let mut ext = Extensions::new();
178        ext.insert(MockVal(1));
179        ext.insert(MockVal(2));
180        assert_eq!(ext.get::<MockVal>().unwrap().0, 2);
181    }
182
183    /// P0:[Extensions::default] 空袋 is_empty 且 len == 0
184    /// 条件:调用 [Extensions::default]
185    /// 断言:is_empty() 为 true,len() == 0
186    #[test]
187    fn default_is_empty() {
188        let ext = Extensions::default();
189        assert!(ext.is_empty());
190        assert_eq!(ext.len(), 0);
191    }
192
193    /// P1:[Extensions::extend] other 覆盖同型、保留异型
194    /// 条件:self 含 MockVal(1) 与 MockStr("self"),other 含 MockVal(2) 与
195    ///       MockNum(7);执行 extend
196    /// 断言:MockVal 为 2(被覆盖)、MockStr 仍在、MockNum 为 7
197    #[test]
198    fn extend_merges_and_overrides() {
199        let mut self_ext = Extensions::new();
200        self_ext.insert(MockVal(1));
201        self_ext.insert(MockStr("self".to_string()));
202
203        let mut other = Extensions::new();
204        other.insert(MockVal(2));
205        other.insert(MockNum(7));
206
207        self_ext.extend(&other);
208        assert_eq!(self_ext.get::<MockVal>().unwrap().0, 2);
209        assert_eq!(self_ext.get::<MockStr>().unwrap().0, "self");
210        assert_eq!(self_ext.get::<MockNum>().unwrap().0, 7);
211        assert_eq!(self_ext.len(), 3);
212    }
213
214    /// P1:[Extensions::clone] 克隆后袋内值 Arc 共享
215    /// 条件:以 `Arc<String>` 为值插入并克隆袋,分别从两袋
216    ///       `get::<Arc<String>>()` 取引用
217    /// 断言:两引用 Arc::ptr_eq 成立(未深拷贝)
218    #[test]
219    fn clone_shares_arcs() {
220        let mut ext = Extensions::new();
221        let shared = Arc::new("hello".to_string());
222        ext.insert(Arc::clone(&shared));
223
224        let cloned = ext.clone();
225        assert!(Arc::ptr_eq(
226            cloned.get::<Arc<String>>().unwrap(),
227            ext.get::<Arc<String>>().unwrap()
228        ));
229    }
230
231    /// P1:[Extensions::remove] 删除后 get / contains 均为空
232    /// 条件:插入 MockVal(3) 后 remove::<MockVal>()
233    /// 断言:remove 返回 Some,随后 contains::<MockVal>() 为 false、get 为 None
234    #[test]
235    fn remove_removes_entry() {
236        let mut ext = Extensions::new();
237        ext.insert(MockVal(3));
238        assert!(ext.remove::<MockVal>().is_some());
239        assert!(!ext.contains::<MockVal>());
240        assert!(ext.get::<MockVal>().is_none());
241        assert!(ext.is_empty());
242    }
243
244    /// P1:[Extensions::contains] 未插入的类型返回 false、已插入返回 true
245    /// 条件:仅插入 MockVal,查询 MockVal 与 MockNum
246    /// 断言:contains::<MockVal>() 为 true,contains::<MockNum>() 为 false
247    #[test]
248    fn contains_detects_type_presence() {
249        let mut ext = Extensions::new();
250        ext.insert(MockVal(1));
251        assert!(ext.contains::<MockVal>());
252        assert!(!ext.contains::<MockNum>());
253    }
254
255    /// P2:[Extensions::Debug] 输出包含 entry 的 Debug 值
256    /// 条件:插入 MockStr("abc") 后格式化
257    /// 断言:Debug 字符串包含 "abc"
258    #[test]
259    fn debug_includes_entry_values() {
260        let mut ext = Extensions::new();
261        ext.insert(MockStr("abc".to_string()));
262        let dbg = format!("{ext:?}");
263        assert!(dbg.contains("abc"), "got: {dbg}");
264    }
265
266    /// P2:[Extensions::insert] 非 Clone 值类型可存入并读取
267    /// 条件:插入 `#[derive(Debug)] struct NonCloneVal(u32)`(无 Clone)
268    /// 断言:get::<NonCloneVal>() 返回 Some 且值正确
269    #[test]
270    fn non_clone_value_is_supported() {
271        let mut ext = Extensions::new();
272        ext.insert(NonCloneVal(9));
273        assert_eq!(ext.get::<NonCloneVal>().unwrap().0, 9);
274    }
275
276    // ── test fixtures ──
277
278    #[derive(Debug)]
279    struct MockVal(u32);
280    #[derive(Debug)]
281    struct MockStr(String);
282    #[derive(Debug)]
283    struct MockNum(u64);
284    #[derive(Debug)]
285    struct NonCloneVal(u32);
286}