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}